TODOアプリ

今回はiOSアプリでリレーショナル・データベースを利用する際によく使われるRealmRealmSwiftを使ってTODOアプリを作成していきます。

講義の目標

  • Realmを使ってデータベースを利用できるようになること

Xcoeeプロジェクトの作成

新規プロジェクトを立ち上げます。その際、使用するテンプレートはSingle View Applicationとなります。

入力項目 入力値
Product Name Todo
Team None
Oganization Name 任意(”ALJ”など自分が所属している組織の名前を入れる)
Oganization Identifier jp.co.al-j
Language Swift
Devices iPhone
Use Core Data チェックを外す
include Unit Tests チェックを外す
include UI Tests チェックを外す

Realmとは

リレーショナル・データベースの一種で、iOSだけでなく、Android版にもある新しいデータベースエンジンです。

Realmの特徴

Realmは下記のような特徴を持っています。

  • 処理速度が早い
  • データベース処理の構文がシンプル
  • テーブル定義もSwiftのクラスファイルで記述

RealmSwiftとは

RealmSwiftはそのRealmをSwiftで使うためのライブラリです。
RealmRealmSwiftを利用する事により高速にデータベースを利用したアプリを作成する事が可能です。

Realm、RealmSwiftの導入方法

RealmRealmSwiftの導入にはCocoaPodsを使っていきます。
ターミナルでプロジェクトフォルダに移動し、下記のコマンドを実行します。

ターミナル操作

pod init

Podfileが作成されますのでそれをテキストエディタで開き、下記の内容へ修正し、保存してください。

ターミナル操作

open Podfile

Podfile

platform :ios, '8.0'
use_frameworks!
target 'Todo' do
pod 'Realm', :git => 'https://github.com/realm/realm-cocoa.git', :branch => 'swift-2.0'
pod 'RealmSwift', :git => 'https://github.com/realm/realm-cocoa.git', :branch => 'swift-2.0'
end

そしてpod installをしてRealmRealmSwiftの導入します。

pod install

下記のようなメッセージがターミナルで表示されればインストール完了です。

Installing Realm (0.98.2)
Installing RealmSwift (0.98.2)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `Todo.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed.

一度開いているXcodeプロジェクトを閉じて、Todo.xcworkspaceをXcodeで開くことによりRealmRealmSwiftを利用して開発できるようになります。

モデルの定義

まずRealmのようなリレーショナル・データベースを利用するためには、モデルを作成しないといけません。モデルとは データベースをどのように構成するかということを定義したものです。

今回はTODOアプリですので、下記のようなモデルを作成する必要があります。

項目名 説明
name TODO項目の名前
desc TODO項目の説明文
isComplete TODO完了有無(trueが完了、falseが未完了)

Xcodeプロジェクトにてプロジェクト内に新しいSwiftクラスを作成します。
下図のとおりNew Fileでファイルを作成してください。
ファイル名はToDo.swiftにします。

ToDo.swiftファイルに上記のモデルを定義していきます。

ToDo.swift

import Foundation
//////////////// ▼▼ 追加 ▼▼ ////////////////
import RealmSwift

class ToDo: Object{
// 名前
dynamic var name = ""
// 説明文
dynamic var desc = ""
// 完了フラグ
dynamic var isComplete = false
// 作成日
dynamic var createDate = NSDate(timeIntervalSince1970: 0)
// 更新日
dynamic var updateDate = NSDate(timeIntervalSince1970: 0)
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

画面レイアウト構築

TODO一覧画面

Main.storyboardで画面レイアウトを構築していきます。
初期表示のViewControllerは4インチサイズにしておきましょう。

次にNavigationControllerを追加してTODOリストとTODO詳細を行き来できるようにします。
メニューバーからEditer -> Embed in -> NavigationControllerを選択してください。

このようにNavigationControllerが追加されます。

ViewControllerにTableViewを配置します。下図のとおりPrototype Cell1にしてください。

TableViewCellを選択して、IdentifierCellにします。

ナビゲーションバーのタイトルをTODO一覧にし、Bar Button Itemを追加してSystem Itemaddにします。

TODO登録・編集画面

次にTODO登録・編集する画面を追加します。Main.storyboardでViewControllerを追加します。
画面サイズも4インチにしておいてください。

まずSegueをつなげておきましょう。SegueIdentifiertoDetailにします。

下図のとおりに、TextFieldLabelTextViewButtonBar Button Itemを追加してください。

配置できたらTodoDetailViewController.swiftファイルを作成し、追加した画面と関連付けます。

次に下図のとおりIBOutlet接続してください。

@IBOutlet weak var titleField: UITextField!
@IBOutlet weak var descTextView: UITextView!
@IBOutlet weak var commitButton: UIButton!
@IBOutlet weak var trashButton: UIBarButtonItem!

TODO登録機能

それではTODO登録機能を追加します。

TodoDetailViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import RealmSwift
//////////////// ▲▲ 追加 ▲▲ ////////////////
class TodoDetailViewController: UIViewController {

@IBOutlet weak var titleField: UITextField!
@IBOutlet weak var descTextView: UITextView!
@IBOutlet weak var commitButton: UIButton!
@IBOutlet weak var trashButton: UIBarButtonItem!

//////////////// ▼▼ 追加 ▼▼ ////////////////
var actionType = "" // アクション種別
var selectedTodo:ToDo! // 前の画面から送られたTODO
//////////////// ▲▲ 追加 ▲▲ ////////////////

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
self.initView()
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
// 画面表示切替
func initView(){
// アクション種別によりタイトル・ボタンのラベル、タイトル、説明を変更
if actionType == "NEW" {
self.title = "TODO新規追加"
self.commitButton.setTitle("新規追加", forState: .Normal)
self.commitButton.addTarget(self, action: "dbAdd:", forControlEvents: .TouchUpInside)
self.titleField.text = ""
self.descTextView.text = ""
self.navigationItem.rightBarButtonItem = nil
} else if actionType == "UPDATE" {
self.title = "TODO編集"
self.commitButton.setTitle("更新", forState: .Normal)
self.commitButton.addTarget(self, action: "dbUpdate:", forControlEvents: .TouchUpInside)
self.titleField.text = selectedTodo.name
self.descTextView.text = selectedTodo.desc
}
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ 追加 ▼▼ ////////////////
// TODO追加
@IBAction func dbAdd(sender: AnyObject){
// 入力チェック
if isValidateInputContents() == false{
return
}
// toDoデータ作成
let toDo = ToDo()
toDo.name = titleField.text!
// 現在の時刻をセット
toDo.createDate = NSDate()
// DB登録
do{
let realm = try Realm()
try realm.write{
realm.add(toDo)
}
print("DB登録成功")
}catch{
print("DB登録失敗")
}
// 前の画面に戻る
self.navigationController?.popViewControllerAnimated(true)
}

// 入力チェック
private func isValidateInputContents() -> Bool{
// 名前の入力
if let title = titleField.text{
if title.characters.count == 0{
return false
}
}else{
return false
}
return true
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

TODO一覧画面から画面遷移できるようにコードを追加します。
TODO登録時にrealmインスタンスを生成してwriteメソッドでTODOデータを保存する処理を実行しています。
また今回はTODO登録・編集画面は、新規登録時、編集時で共有して使いますので、新規作成アクション時にはactionTypeNEWにセットして画面遷移します。

ViewController.swift

class ViewController: UIViewController {
//////////////// ▼▼ 追加 ▼▼ ////////////////
var actionType = "" // アクション種別(NEW or UPDATE)
var selectedTodo:ToDo! // 選択したTODO
//////////////// ▲▲ 追加 ▲▲ ////////////////
override func viewDidLoad() {
super.viewDidLoad()
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//////////////// ▼▼ 追加 ▼▼ ////////////////
@IBAction func addButtonTapped(sender: AnyObject) {
// TODO編集画面へ遷移
self.actionType = "NEW"
self.performSegueWithIdentifier("toDetail", sender: nil)
}
// 画面遷移時に呼ばれる
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// 遷移時にアクション種別と(更新時に)選択されたTODOリストを送る
if segue.identifier == "toDetail" {
let vc = segue.destinationViewController as! TodoDetailViewController
vc.actionType = self.actionType
}
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

IBActionメソッドを追加したので、Main.storyboardで関連付けしておきましょう。

それではこの状態で一度Runして確認してみましょう。デバッグエリアに失敗というメッセージが出ていなければRealmにデータが保存されています。

Realm Browserでデータを確認

今保存したRealmのデータをRealm Browserというツールを使って確認してみましょう。
下記URLをクリックしてRealm Browserをインストールしましょう。

https://itunes.apple.com/jp/app/realm-browser/id1007457278?mt=12

Realmデータ確認方法

Realm Browserをインストール完了して、Realmを使ったiOSアプリをシュミレータで動作させた場合には、下記のコマンドをターミナルで実行すれば、Realm Browserで中身を開く事ができます。

ターミナルで実行

open $(find ~/Library/Developer/CoreSimulator/Devices/$(ls -t1 ~/Library/Developer/CoreSimulator/Devices/ | head -1)/data/Containers/Data/Application/ -name \*.realm)

下記のような画面が出ますのでAllowをクリックしてください。

このようにRealm Browsetでデータの中身を確認できます。

TODO一覧表示機能

それではRealmに保存されたデータをTODO一覧画面に表示させましょう。
ViewController.swiftにコードを追加してください。

ViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import RealmSwift
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ 追加 ▼▼ ////////////////
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
//////////////// ▲▲ 追加 ▲▲ ////////////////
//////////////// ▼▼ 追加 ▼▼ ////////////////
@IBOutlet weak var tableView: UITableView! // TableView
// Realm Todoアイテムコレクション
var todoItems:Results<ToDo>?{
do{
let realm = try Realm()
return realm.objects(ToDo).sorted("createDate")
}catch{
print("エラー")
}
return nil
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
var actionType = "" // アクション種別(新規 or 編集)
var selectedTodo:ToDo! // 選択したTODOアイテム

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
tableView.delegate = self
tableView.dataSource = self
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
// 画面が表示する度に呼ばれる
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// TableView更新
tableView.reloadData()
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
:
:
//////////////// ▼▼ 追加 ▼▼ ////////////////
// MARK: - TableViewDataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todoItems!.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let toDo = todoItems?[indexPath.row]
cell.textLabel?.text = toDo?.name
return cell
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

メンバ変数にvar todoItems:Results<ToDo>を定義する事により、ToDoのデータオブジェクト形式のRealmデータを常に保持するようになります。こうする事によりRealmのToDoデータが更新される度にこのtodoItemsの中もデータが更新されるようになります。
そのtodoItemsの配列をもとにTableViewDataSourceにてテーブルビューの表示仕様を確定させ、画面にTODOデータを表示させています。

IBOutletを追加しましたので、Main.storyboardを開いて関連付けしてください。

それではRunして動作確認してみましょう。TODO一覧画面にて登録したTODOアイテムが表示されていればOKです。

TODO編集機能

次にTableViewCellをタップしたらTODO編集・登録画面へ遷移し、登録しているTODOを編集できるようにします。ViewController.swiftにてセルタップ時の処理を追加し、actionTypeUPDATEにし、選択されたセルのTodoデータを画面遷移時に送信するようにします。

ViewController.swift

// 画面遷移時に呼ばれる
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// 遷移時にアクション種別と(更新時に)選択されたTODOリストを送る
if segue.identifier == "toDetail" {
let vc = segue.destinationViewController as! TodoDetailViewController
vc.actionType = self.actionType
//////////////// ▼▼ 追加 ▼▼ ////////////////
vc.selectedTodo = selectedTodo
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
// TableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// TODOデータを次に送る
self.actionType = "UPDATE"
selectedTodo = todoItems?[indexPath.row]
self.performSegueWithIdentifier("toDetail", sender: selectedTodo)
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

次に、TodoDetailViewControllerで、DBを更新する処理を追加します。

//////////////// ▼▼ 追加 ▼▼ ////////////////
// DB更新
@IBAction func dbUpdate(sender: AnyObject){
do{
let realm = try Realm()
try realm.write{
selectedTodo.name = self.titleField.text!
selectedTodo.desc = self.descTextView.text
selectedTodo.updateDate = NSDate()
}
print("DB更新成功")
}catch{
print("DB更新失敗")
}
// 前の画面に戻る
self.navigationController?.popViewControllerAnimated(true)
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

この状態でRunして確認してみましょう。登録済みのTODOが更新できる事を確認してください。

TOOD削除機能

最後にTODOを削除する機能を追加します。

TodoDetailViewController.swift

// 削除
@IBAction func dbDelete(sender: AnyObject) {
//////////////// ▼▼ 追加 ▼▼ ////////////////
do{
let realm = try Realm()
try realm.write{
realm.delete(selectedTodo)
}
}catch{
print("失敗")
}
self.navigationController?.popViewControllerAnimated(true)
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

追加したIBActionをMain.storyboardで関連付します。

これでTODOアプリは完成です。
RealmRealmSwiftを使うことにより簡単にデータベースを利用できる事ができます。
今回作成したTodoアプリは非常にシンプルなものですが、完了期日を設定したり、タグをつける機能をつけたりして是非高機能にしていってください。