Swift

【Swift】Realmを使ってTableViewの編集で追加・削除・並び替えをする

「UITableView」でアイテムの追加・削除・並び替えをし、それを「Realm」に反映させる方法を解説していきます

並び替えには「Realm」の「List」を利用します

この記事は前の記事の続きとなっておりますので先にそちらをご覧ください

前の記事ではTableViewの編集モードで削除や並び替えをし、それを配列に反映させるようにしました

ここではローカルなデータベースである「RealmSwift」に編集したデータを保存していきます

RealmSwiftをインストールする

「Cocoapods」で「RealmSwift」をインストールしていきますので「Cocoapods」をインストールしていない場合は先に「Cocoapods」をインストールしてください

RealmSwiftのインストール

まず、ターミナルでプロジェクトファイルまで移動し、「pod init」を行います

pod init

「Podfile」が作られたら下のように編集します

use_frameworks!

target 'UITableView' do
pod 'RealmSwift'
end

保存してファイルを閉じます

最後に、「pod install」をします

pod install

「プロジェクト名.xcworkspace」というファイルが作成されたと思うのでそれを開きます

Realmのデータベースの中身を見る

Realmのデータベースの中身を確認したいときには「RealmBrowser」や「RealmStudio」というものを使いますが今回は「RealmStudio」を見ながら説明していきます

ここでは「RealmStudio」の使い方は説明しておりません

コードを書く

全体のコードはページの一番最後に載せてあります

RealmSwiftを使う

まず、「import RealmSwift」を書き、モデルクラスを作成します

もし「import」でエラーになる場合は「command + B」でビルドしてから試してみてください

ここでは同じファイルに書いていきますが、モデルクラスは別のファイルで作成したほうがいいと思います

import RealmSwift

class Item: Object {
    @objc dynamic var title: String = ""
}

class ItemList: Object {
    let list = List<Item>()
}

「Itemクラス」ではアイテム名を保存する「変数title」だけ定義します

「List」は「Realm」の配列のようなもので「ItemList」は並び替え時に順番を保持するために使います

定数「list」には「Item型」が入った「List」を定義します

「Itemクラス」の変数がひとつの場合は「List<String>()」というように書けるため、ひとつのクラスで済みますが、「Itemクラス」に他の変数を追加することを想定して「List」のクラスを別に作っています

「ViewController」クラス内に「realm」と「list」を定義します

「List」は配列のようなものなのでここでは「Itemクラス」が入った配列を宣言している感じです

    let realm = try! Realm()
    var list: List<Item>!

次に、「viewDidLoad」のなかで「Realm」のデータの場所を出力する処理を書きます

「RealmStudio」や「RealmBrowser」などを使う場合に利用してください

「list」を定義していきます

print(Realm.Configuration.defaultConfiguration.fileURL!)
list = realm.objects(ItemList.self).first?.list

ここでは「ItemList」の一番目にアイテムが入っていたら「list」に入ります

初めて起動したときには「nil」になりますが、のちにアイテムを追加した後に起動すると一番目の「list」が入ります

下の写真はのちにアイテムを追加した後のデータです

tableViewに表示するセルの数と表示する内容を変える

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return realm.objects(Item.self).count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
        cell.textLabel?.text = list[indexPath.row].title
        return cell
    }

追加のボタンを押した時の処理

    @objc func addButtonPressed(_ sender: UIBarButtonItem) {
        var textField = UITextField()
        let alert = UIAlertController(title: "新しいアイテムを追加", message: "", preferredStyle: .alert)
        let action = UIAlertAction(title: "追加", style: .default) { (action) in
            let item = Item()
            item.title = textField.text!
            try! self.realm.write() {
                if self.list == nil {
                    let itemList = ItemList()
                    itemList.list.append(item)
                    self.realm.add(itemList)
                    self.list = self.realm.objects(ItemList.self).first?.list
                } else {
                    self.list.append(item)
                }
            }
            self.tableView.reloadData()
        }
        alert.addTextField { (alertTextField) in
            alertTextField.placeholder = "アイテムを入力"
            textField = alertTextField
        }
        
        alert.addAction(action)
        present(alert, animated: true, completion: nil)
    }

ここでは「Itemクラス」をインスタンス化し、「変数title」に「textField.text」を入れます

「list」が「nil」だった場合は「ItemList」の「list」に「item」をappendし、その「ItemList」を「realm.add」します

そして「list」に「realm.objects(ItemList.self).first?.list」を入れます

「list」が「nil」でない場合は「list」に「item」をappendします

セルが削除された時の処理

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            try! realm.write {
                let item = list[indexPath.row]
                realm.delete(item)
            }
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
    }

並び替えの処理

    func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
        try! realm.write {
            let listItem = list[fromIndexPath.row]
            list.remove(at: fromIndexPath.row)
            list.insert(listItem, at: to.row)
        }
    }

配列を並び替えた時と同じように「list」を並び替えています

並び替えをした後に「RealmStudio」をみると、「list」だけ並び替えの順番が保持されていることがわかります

全体のコード


import UIKit
import RealmSwift

class Item: Object {
    @objc dynamic var title: String = ""
}

class ItemList: Object {
    let list = List<Item>()
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    let realm = try! Realm()
    var list: List<Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        print(Realm.Configuration.defaultConfiguration.fileURL!)
        list = realm.objects(ItemList.self).first?.list
        
        let addBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonPressed(_ :)))
        navigationItem.rightBarButtonItems = [editButtonItem, addBarButtonItem]
    }
    
    @objc func addButtonPressed(_ sender: UIBarButtonItem) {
        var textField = UITextField()
        let alert = UIAlertController(title: "新しいアイテムを追加", message: "", preferredStyle: .alert)
        let action = UIAlertAction(title: "追加", style: .default) { (action) in
            let item = Item()
            item.title = textField.text!
            try! self.realm.write() {
                if self.list == nil {
                    let itemList = ItemList()
                    itemList.list.append(item)
                    self.realm.add(itemList)
                    self.list = self.realm.objects(ItemList.self).first?.list
                } else {
                    self.list.append(item)
                }
                
            }
            self.tableView.reloadData()
        }
        alert.addTextField { (alertTextField) in
            alertTextField.placeholder = "アイテムを入力"
            textField = alertTextField
        }
        
        alert.addAction(action)
        present(alert, animated: true, completion: nil)
    }
    
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        tableView.setEditing(editing, animated: animated)
        tableView.isEditing = editing
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return realm.objects(Item.self).count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
        cell.textLabel?.text = list[indexPath.row].title
        return cell
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {

            try! realm.write {
                let item = list[indexPath.row]
                realm.delete(item)
            }
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
    }
    
    func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
        try! realm.write {
            let listItem = list[fromIndexPath.row]
            list.remove(at: fromIndexPath.row)
            list.insert(listItem, at: to.row)
        }
    }
    
    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }
}