この記事では、SwiftUIでのCoreData の基本的な実装方法【CRUD(作成、更新、削除)】を紹介しています。
簡単なタスク管理App(タスク内容とスケジュールの登録)を作成しながら解説します。
◆動作検証環境
・XCode:12.1
・SwiftUI:2.0
・iOS:14.0
・Life Cycle:SwiftUI App
SwiftUIでCoreDataを利用するプロジェクトの作成
まずは、プロジェクトの作成です。
Use Core Dataにチェックをいれて作成します。
SwiftUIでCoreDataを利用する際の初期コードの設定
プロジェクト作成の際にUse Core Dataにチェックを入れると、予めContextView.swift とPresistence.swift にCoreData操作に利用するコードが記載されています。
今回は、オリジナルの操作方法(VMMV)にするため対象となるコードを削除します。
削除後のコードを、一旦以下のようにします。
ContextView.swift
1 2 3 4 5 6 7 8 9 10 11 | import SwiftUI import CoreData struct ContentView: View { var body: some View { Text("Hello") } } |
Presistence.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import CoreData struct PersistenceController { static let shared = PersistenceController() let container: NSPersistentContainer init(inMemory: Bool = false) { container = NSPersistentContainer(name: "CoreDataTest") if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) } } |
AppNameApp.swift もCore Data利用に対応するコードとなります。
こちらのファイルはデフォルトのまま利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import SwiftUI @main struct CoreDataTestApp: App { let persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) } } } |
次に.xcdatamodeldファイルを編集します。
Entity名をTaskとし、Attributeにdate(Date型)、content(String型)を追加しました。
編集後は、変更をプロジェクトに反映させるためにビルドします(command+B )。
SwiftUIでCoreDataを利用する際のデータの登録、編集
CoreDataへデータを登録するコードを紹介します。
今回は、以下のようなSheetを作成して、タスクの内容とスケジュールを入力し登録、編集を行います。
ViewModel.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | import SwiftUI import CoreData class ViewModel : ObservableObject{ @Published var content = "" @Published var date = Date() @Published var isNewData = false @Published var updateItem : Task! func writeData(context : NSManagedObjectContext ){ if updateItem != nil{ updateItem.date = date updateItem.content = content try! context.save() updateItem = nil isNewData.toggle() content = "" date = Date() return } let newTask = Task(context: context) newTask.date = date newTask.content = content do{ try context.save() isNewData.toggle() content = "" date = Date() } catch{ print(error.localizedDescription) } } func EditItem(item:Task){ updateItem = item date = item.date! content = item.content! isNewData.toggle() } } |
ポイント
11行目:データ登録、編集時のメソッド
新規登録として呼ばれた場合は、If文より下のコードが利用されCoreDataへデータが書き込まれる。
データ書き込み後は、Sheetを隠し、次回の登録に向けタスク内容と、スケジュール内容を初期化する
編集(更新)として呼ばれば場合は、If文内のコードが利用され、編集後のデータが書き込まれる。
データ書き込み後は、新規登録同様に関係データを初期化する。
40行目:
データ編集用のメソッド
すでに登録されているデータを編集するために利用する。
元となるデータを予め表示する形で、登録用のSheetを表示する。
書き込みは、上記で紹介した登録用のメソッドを利用する。
NewDataSheet.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | import SwiftUI struct NewDataSheet: View { @ObservedObject var viewModel : ViewModel @Environment(\.managedObjectContext) var context var body: some View { VStack{ HStack{ Text("\(viewModel.updateItem == nil ? "Add New" : "Update") Task") .font(.title) .fontWeight(.heavy) .foregroundColor(.primary) } .padding() TextEditor(text: $viewModel.content) .padding() Divider() .padding(.horizontal) HStack{ Text("When") .font(.title) .fontWeight(.bold) .foregroundColor(.primary) } .padding() DatePicker("", selection:$viewModel.date, displayedComponents:.date)//日付も使用する場合は”displayedComponents:.date”をなくす .labelsHidden() Button(action: {viewModel.writeData(context: context)}, label: { Label(title:{Text(viewModel.updateItem == nil ? "Add" : "Update") .font(.title) .foregroundColor(.white) .fontWeight(.bold) }, icon: {Image(systemName: "plus") .font(.title) .foregroundColor(.white) }) .padding(.vertical) .frame(width:UIScreen.main.bounds.width - 30) .background(Color.orange) .cornerRadius(50) }) .padding() .disabled(viewModel.content == "" ? true : false) .opacity(viewModel.content == "" ? 0.5 : 1) } .background(Color.primary.opacity(0.06).ignoresSafeArea(.all, edges: .bottom)) } } |
ポイント
5,6行目:
ViewModelのデータ利用のため、@ObservedObjectの変数定義。
managedObjectContextデータ利用のための、@Environmentの変数定義
11、33行目:
Sheetを表示する際に、編集用のデータがあるかないかで、Sheetのタイトル、書き込み用のボタンのラベル名を変更する。
18、29行目:
CoreDataへ登録する、タスク内容とスケジュールを入力する。
48,49行目:
タスク内容が無い状態で書き込まれるのを防ぐための対応。
SwiftUIでCoreDataを利用する際のデータの表示、削除
CoreDataに登録されているデータを表示、削除するコードを紹介します。
今回は、以下のようなViewを作成して、タスクの内容とスケジュールの表示、削除を行います。
ContentView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | import SwiftUI import CoreData struct ContentView: View { @StateObject var viewModel = ViewModel() @FetchRequest(entity: Task.entity(), sortDescriptors: [NSSortDescriptor(key: "date", ascending: true)],animation: .spring()) var results : FetchedResults<Task> @Environment(\.managedObjectContext) var context var body: some View { ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom), content: { VStack(spacing:0){ HStack{ Text("Tasks") .font(.largeTitle) .fontWeight(.heavy) .foregroundColor(.black) Spacer(minLength: 0) } .padding() .padding(.top,UIApplication.shared.windows.first?.safeAreaInsets.top) .background(Color.white) if results.isEmpty{ Spacer() Text("No Tasks") .font(.title) .foregroundColor(.primary) .fontWeight(.heavy) Spacer() }else{ ScrollView(.vertical,showsIndicators: false, content:{ LazyVStack(alignment: .leading, spacing: 20){ ForEach(results){task in VStack(alignment: .leading, spacing: 5, content: { Text(task.content ?? "") .font(.title) .fontWeight(.bold) Text(task.date ?? Date(),style: .date) .fontWeight(.bold) Divider() }) .foregroundColor(.primary) .contextMenu{ Button(action: { viewModel.EditItem(item: task) }, label: { Text("Edit") }) Button(action: { context.delete(task) try! context.save() }, label: { Text("Delete") }) } } } .padding() }) } } Button(action: {viewModel.isNewData.toggle()}, label: { Image(systemName: "plus") .font(.largeTitle) .foregroundColor(.white) .padding(20) .background(Color.green) .clipShape(Circle()) }) .padding() }) .ignoresSafeArea(.all, edges: .top) .background(Color.primary.opacity(0.06).ignoresSafeArea(.all, edges: .all)) .sheet(isPresented: $viewModel.isNewData, content: { NewDataSheet(viewModel: viewModel) }) } } |
ポイント
8行目:@FetchRequest変数の利用
CoreDataに登録されているデータを取得する。
sortDescriptors: [NSSortDescriptor(key: "date", ascending: true)] と指定する事で、日付のデータを元に昇順で表示します。
25行目:
CoreDataのデータが無い場合の表示
46行目:
.contextMenu{} として更新、削除を行う。編集の場合は、ViewModelクラスの編集用のメソッドを呼ぶ
65行目:
新規データ登録としてSheetを表示し、登録を行う。
以上、SwiftUIでのCoreData の基本的な実装方法【CRUD(作成、更新、削除)】を紹介しました。