Zubora Code

SwiftUI x SwiftData でシンプルなメモアプリ(データの永続化を含む)を作成する

SwiftUI x SwiftData でシンプルなメモアプリ(データの永続化を含む)を作成する手順をまとめます。

Published: 8 January, 2024
Revised: 8 January, 2024

概要

SwiftUIでアプリ開発を行う上でデータの永続化が必要になったので、SwiftData を使った簡単なメモアプリをサンプル実装することで使い方をまとめておきます。

SwiftUIでの永続化について調べていたら Core Data がよく登場していたので最初は Core Data を使おうと思っていましたが、プロジェクト作成時の Storage の箇所に SwiftData という文言があって調べてみたら、どうやら WWDC 2023 で公開されたばかりの新機能のようです。せっかくなので使ってみたいと思います。

余談ですが、こういう新しい技術は仕事の場合は情報量が少ない内は極力使いたくないですが、個人プロジェクトだったらどんどん使っていく方針です。


作るアプリの概要

以下のメモの作成、表示、削除機能を実装します。


ソースコード

https://github.com/tkugimot/EasyMemo


実装の概要

以下のファイルを追加します。

ファイル名

役割

Domains/Memo/Memo

SwiftDataで扱うメモのモデル

Screens/MemoListScreen

メモの表示と削除、新規作成への遷移元を表示する画面

Views/AddMemoView

新規のメモを追加する画面


実装

プロジェクトの作成

EasyMemo という Product Name でアプリを作成します。Storageに SwiftData を指定します。

Item というモデルとそれを使ったサンプル実装が入った状態で作成されます。不要なので適宜削除してください。



保存するデータのモデルを作成

import Foundation
import SwiftData

@Model
final class Memo {
    var content: String
    var createdAt: Date
    var updatedAt: Date
    
    init(content: String) {
        self.content = content
        createdAt = Date()
        updatedAt = Date()
    }
}


メモの新規作成画面を作成

struct AddMemoView: View {
    @Environment(\.modelContext) private var context
    @Environment(\.presentationMode) var presentation
    
    @State private var content: String = ""
    
    var body: some View {
        VStack {
            Form {
                TextField(
                    text: $content,
                    prompt: Text("Required"),
                    axis: .vertical
                ) {
                    Text("Content")
                }
                .lineLimit(5...10)
            }
        }
        .navigationBarTitle("Add new memo")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button(action: {add()}) {
                    Text("Save")
                }
            }
        }
    }
    
    private func add() {
        let newMemo = Memo(content: content)
        context.insert(newMemo)
        
        // go back to previous page
        presentation.wrappedValue.dismiss()
    }
}

#Preview {
    AddMemoView()
}


メモのリスト画面を作成

import SwiftUI
import SwiftData

struct MemoListScreen: View {
    @Environment(\.modelContext) private var context
    @Query private var memos: [Memo]
    
    var body: some View {
        NavigationView {
            List {
                ForEach(memos) { memo in
                    NavigationLink {
                        VStack(alignment: .leading) {
                            Text(memo.content)
                                .font(.title)
                                .frame(
                                    minWidth: 0,
                                    maxWidth: .infinity,
                                    minHeight: 0,
                                    maxHeight: .infinity,
                                    alignment: .topLeading)
                                .padding()
                        }
                        .padding()
                    } label: {
                        Text(memo.content)
                            .font(.subheadline)
                            .frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: .leading)
                            .lineLimit(1)
                    }
                }
                .onDelete(perform: { indexSet in
                    for index in indexSet {
                        delete(memo: memos[index])
                    }
                })
            }
            .navigationTitle("Memo")
            .navigationBarTitleDisplayMode(.automatic)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    NavigationLink(destination: AddMemoView()) {
                        Text("Add")
                    }
                }
            }
        }
    }

    private func delete(memo: Memo) {
        context.delete(memo)
    }
}

#Preview {
    MemoListScreen()
        .modelContainer(for: Memo.self)
}


ContentViewを修正

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext

    var body: some View {
        MemoListScreen()
    }
}

#Preview {
    ContentView()
        .modelContainer(for: Memo.self, inMemory: true)
}

感想

正直 SwiftData の使い易さにかなりビックリしました。ボイラープレート的なコードも全然なく、無駄のないinterface設計に感銘を受けました。最近のSwiftは全体的にかなり実装し易く整理されている印象で、今すごくアプリ開発に前向きになれています。Appleの皆さんありがとうございます。

参考リンク

Toshimitsu Kugimoto

Software Engineer

仕事では決済やメディアのWebやスマホアプリのBE開発、WebのFE開発をやっています。 JavaとTypeScriptをよく使います。プライベートではFlutterでのアプリ開発にも挑戦中です。