CoreDataの勉強のため、Swiftで簡単なTodoアプリを作ってみた。

仕様について、UIはSwiftUIで一画面のビューを実装し、データの保存にCoreDataを使う。

それでは早速実装したコードを紹介しながら、各コードでどのような処理をしているかを解説していく。

コード全文

まず、コード全文を載せておく。

import SwiftUI
import CoreData

struct ContentView: View {
  @FetchRequest(
    sortDescriptors: [NSSortDescriptor(keyPath: \Task.name, ascending: false)],
    animation: .default
  )
  var tasks: FetchedResults<Task>
  
  @Environment(\.managedObjectContext) var viewContext
  @State var new = ""
  
  var body: some View {
    VStack {
      HStack {
        TextField("Input here", text: $new)
          .textFieldStyle(RoundedBorderTextFieldStyle())
        Button(action: {
          let newTask = Task(context: self.viewContext)
          newTask.name = self.new
          do {
            try self.viewContext.save()
          } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
          }
          
          self.new = ""
        }) {
          Text("追加")
        }
      }
      
      List {
        ForEach(tasks, id: \.self) { task in
          Text("\(task.name!)")
        }
      }
    }.padding()
  }
}

なお、プロジェクト作成時には「Use Core Data」へチェックを入れておくことが前提だ。

また、事前に下記のとおりエンティティを定義しておく。

CoreDataからデータを取得する

CoreDataからデータを取得するには、簡単にデータを取り出せるようFetchRequestプロパティラッパー(コード5行目)が定義されている。

FetchRequestは、DynamicPropertyプロトコルを採用しているので、関連するビューのプロパティも自動更新してくれるようになっている。

@FetchRequest(
  sortDescriptors: [NSSortDescriptor(keyPath: \Task.name, ascending: false)],
  animation: .default
)

今回の取得条件は、sortDescriptorsでTask.nameをキーとして降順(新しいタスクが上)でフェッチするように指定している。

フェッチした結果はFetchResults型として返されるので、以下のようにして取得する。

var tasks: FetchedResults<Task>

データベース操作で必要なコード

CoreDataを使ったデータベース操作にはNSManagedObjectContextのインスタンスが必要になるので、以下でインスタンスを取得する。(11行目)

@Environment(\.managedObjectContext) var viewContext

タスク追加用のフィールドとボタンを用意する

ここからはビューの実装が絡んでくる。(12行目以降)

まず、タスク名を入力するTextFieldを用意し、状態変数newをバインドさせておく。(17行目)

TextField("Input here", text: $new)
  .textFieldStyle(RoundedBorderTextFieldStyle())

19行目でタスク追加ボタンのビューを用意しているが、このとき20行目からCoreDataへのデータ追加処理をおこなっている。

let newTask = Task(context: self.viewContext)
newTask.name = self.new

まずTaskクラスのインスタンスであるnewTaskを生成し、name属性に入力した値self.newを設定している。

このときTaskクラスのイニシャライザには、ManagedObjectContextを渡す必要がある。

そして22行目からデータベースへの保存処理をおこなっていく。

do {
  try self.viewContext.save()
} catch {
  let nsError = error as NSError
  fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}

saveメソッドはエラーを返す可能性があるので、必ずtry/catch文を使った例外処理を書いておく必要がある。

取得したデータの一覧表示

ここまで、CoreDataを使ってデータベースにデータを追加する処理を書いてきたが、次はCoreDataから取得してきたデータをListビューに一覧表示する処理を書いていく。

List {
  ForEach(tasks, id: \.self) { task in
    Text("\(task.name!)")
  }
}

35行目でListビューを定義しており、ForEach文を使ってあらかじめ取得しておいたtasksを出力している。

Todoアプリの完成

完成したアプリをビルドした結果がこちら。

追加したタスクはリストにどんどんスタックされていき、アプリをいったん落とし再起動しても、追加したタスクが残っている。

この簡易Todoアプリをベースに少しずつ改良を加え、CoreDataへの理解を深めていこうと思う。