0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Realmの「逆方向の関連」を利用してTodoリストを作る

Last updated at Posted at 2020-12-11

TableViewの勉強を進めていると、Sectionが複数になった途端コードが散らかってしまいませんか?
2次元配列にしたり、Sectionクラスを作ってプロパティとしてrow要素を組み込んだり方法は色々ありますが、Realm の逆方向の関連を使うと比較的綺麗にまとまったので紹介します。

Sectionとrowだけでなく、一対多のネスト構造のもの全般に活躍できそうです。例えば、メインオブジェクトはゲームソフトだけど、新規作成はゲームの記録をメインとしたUIを想定している場合でも、Realmの逆方向の関連を活用すればUIに沿った形でコードが作成できるかと思います。

今回は、Realmの逆方向の関連を用いて、簡単なTodoアプリの新規作成機能を作りたいと思います。

環境 
Swift 5.2
Xcode 11.4
Realm 10.4.0

Realmのインストールが済んだ状態から進めます。

todo.swift

import FonDation
import RealmSwift

class Section: Object {
    @objc dynamic var name: String = ""
    //Taskオブジェクトとの逆方向の関連を明示
    let tasks = LinkingObjects(fromType: Task.self, property: "section")
}

class Task: Object {
    @objc dynamic var section: Section?
    @objc dynamic var name: String = ""
}

sectionに格納されるSectionクラスと、rowに格納されるTaskクラスは、一対多の関係なので、SectionクラスのプロパティにTaskをネストさせるのが自然です。が、どちらかというとSectionよりもTaskをメインオブジェクトとして扱いたいのと、後々検索やソートをかける時などTaskにSectionの情報が直接入っている方が便利な場合も多いため、TaskのプロパティにSectionを入れています。
新しく作ったTaskのsectionプロパティが既存のSectionオブジェクトと一致すると、自動でその配下に入る仕組みにより、SectionクラスとTaskクラスの一対多のネスト関係が成り立っています。これが逆方向の関連の強みの一つだと思います。

新しく作ったTaskオブジェクトのsectionプロパティが既存のSectionオブジェクトと一致した場合に、自動でその配下へ追加される仕組みです。ViewControllerは例えばこのような感じになります。

viewController.swift
import UIKit
import RealmSwift

class ViewController: UIViewController {

    
    @IBOutlet weak var tableView: UITableView!
    
    //既存のSectionを間違いなく選択できるようにする
    var pickerView: UIPickerView = UIPickerView()
    //テーブルビューの元の配列
    var list: Results<Section>?
    //Realmオブジェクト作成
    let realm = try!Realm()
    
    //タスク名を入れるテキストフィールド
    @IBOutlet weak var taskTextField: UITextField!
    
    //セクション名を入れるテキストフィールド
    @IBOutlet weak var SectionTextField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //Sectionオブジェクトを全て呼び出しlist配列に格納
        list = realm.objects(Section.self)
        
        setUpTable()
        callPickerView()
    }
    
    //追加ボタン
    @IBAction func addButton(_ sender: Any) {
        
        do {
            try realm.write{
                //セクションのテキストフィールドの文字を元にRealmから呼び出す
                let section = realm.objects(Section.self).filter("name = '\(SectionTextField.text ?? "")'").first
                //タスクの作成、呼び出したsectionを格納、なければ新しくインスタンス作成
                let task = Task(value: ["name": taskTextField.text ?? "不明",
                                    "section": section ?? Section(value: ["name": SectionTextField.text ?? "不明"])])
                //新規タスクを追加
                realm.add(task)
            }
        }catch {
            
        }
        tableView.reloadData()
    }


}

extension ViewController {
    
    //tableViewのセットアップ
    func setUpTable(){
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "taskCell")
        tableView.reloadData()
    }
    
    //SectionのテキストフィールドをタップするとpickerViewが呼び出される
    func callPickerView() {
        pickerView.delegate = self
        pickerView.dataSource = self
        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50))
        let item1 = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done))
        let flexibleItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
        toolbar.setItems([item1, flexibleItem], animated: true)
        
        SectionTextField.inputView = pickerView
        SectionTextField.inputAccessoryView = toolbar
    }
    
    //PickerViewの完了ボタンをタップした時の処理
    @objc func done() {
        if let list = list {
            SectionTextField.text = "\(list[pickerView.selectedRow(inComponent: 0)].name)"}
        else {
            return
        }
        SectionTextField.endEditing(true)
    }
}

extension ViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       
            return list?[section].tasks.count ?? 0
        
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       // guard let count = timeCardData?.count, indexPath.row < count else { return cell }
        let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath)
        cell.textLabel?.text = list?[indexPath.section].tasks[indexPath.row].name
        return cell
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        guard let list = list else {
            return "no Section"
        }
        return list[section].name
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return list?.count ?? 0
    }
}

extension ViewController: UITableViewDelegate {
    
}

extension ViewController: UIPickerViewDelegate {
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return list?.count ?? 0
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return list?[row].name ?? ""
    }
}

extension ViewController: UIPickerViewDataSource  {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
}


0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?