見出し画像

【じっくりSw1ftUI72】実践(DB)編41(ラスト)〜第53章 SwiftDataチュートリアル〜長かった連載も今日でひと区切り笑🤣

さてと、前回

で、

SwiftDataのガイド

についてはやったけど、例の如く、

今回やるコードを、抜粋して、部分部分で解説してただけ
あんなの、あれだけ読んでわかったってゆーてたら逆に怖い
😱
(ま、どのプログラミング言語を使うどこの現場でも、自称さんほど、
それでもわかるのがプロ=分からないが素直に言えない
👉だから一生、自称プロで終わる人で多いのだが。)

今回は、実際のコードを見ながら、作り込んでく〜〜〜

SwiftUIで使えるデータベース関連の記事は、

◾️CoreData

◾️CloudKit

◾️SwiftData

今回の記事
って感じで、この本

のプロットに従って、順番にやってきてはいるんだけど、おそらく、

(DB周りの)各フレームワークの特徴を、
簡単に比較した記事なんかが欲しい

って人もいるだろうから、

で書いたみたいなちょいコラで、次章以降の番外編に行く前に、記事にしようかなと😛ま、今回のSwiftDataまでで、

SwiftUIを使ったiOSの大抵のスマホアプリは、
自分で作れるような知識は網羅できたつもり

だから、これまでの記事で何回か書いてきてると思うけど、基本のコードと動きについては、

で書いてきた今までの記事を見返せばわかるはずだから、そんなもん覚えようとするより、

自分が作りたいアプリを考える方が重要かなあ🧐

それさえ決まれば、後は基本のコードを組み合わせて、自分なりのアプリを作れば良いだけなんで〜〜〜〜

毎度、オイラの学び直しなんざ関係ないって人は、

に全部コードは載ってそうだから、そっちを参考にしたら良いんじゃね
(いつまでそこのサイトが有効かは知らないけど藁😛)
さてと、んだば早速本題へ〜〜〜


じっくり第53章を読んでく👓

ま、ここではチャチャっと、これから前回やったSwiftDataのガイドの実践例をデモンストレーションでやってくぜ🕺って感じで書いてんね🤣こんな解説を掘り下げても仕方ないので〜〜〜

さっそく操作

まずは、今回用のプロジェクトを今回も作成

前回と同じ要領で作成して、余計なコードやファイルを今回は一切削除して、

ContentView
Appファイル

くらいで良いかなと👀

モデルクラスを作成

import SwiftUI
import SwiftData
import Foundation

@Model
class Menu {
    var name: String
    var amount: String
    
    init(name: String, amount: String) {
        self.name = name
        self.amount = amount
    }
}
てな感じ

ログ用のモデルクラスを追加

@Model
class FruitsLog {
    var dateEntry: Date
    init(dateEntry: Date) {
        self.dateEntry = dateEntry
    }
}
てな感じ

モデルコンテナをAppファイルに追加

import SwiftUI
import SwiftData

@main
struct E53SwiftDataTutirialsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for:Menu.self)
    }
}
てな感じ

項目リストビューを作って、アクセスとクエリを追加

struct MenuListView: View {
    @Environment(\.modelContext) var modelContext
    @Query var fruitsMenu: [Menu]
    
    var body: some View {
        List{
            
        }
    }
}
てな感じ

MenuListViewの中身を構築

struct MenuListView: View {
    @Environment(\.modelContext) var modelContext
    @Query var fruitsMenu: [Menu]
    
    var body: some View {
        List{
            ForEach(fruitsMenu){ menu in
                NavigationLink(value: menu){
                    if(menu.amount.isEmpty){
                        Text("新しい項目を編集")
                            .foregroundStyle(Color.gray)
                    } else {
                        Text("\(menu.amount),\(menu.name)")
                    }
                }
            }
            .onDelete(perform: deleteMenu)
        }
    }
    func deleteMenu(_ indexSet: IndexSet){
        for idx in indexSet {
            let menu = fruitsMenu[idx]
            modelContext.delete(menu)
        }
    }
}

#Preview {
    MenuListView()
}
てな感じ

モデルクラスとのリレーション(相関関係)を確立

@Model
class Menu {
    var name: String
    var amount: String
    
    @Relationship(deleteRule: .cascade) var menuLogs = [FruitsLog]()
    
    init(name: String, amount: String) {
        self.name = name
        self.amount = amount
    }
}

項目詳細ビューを追加して、バインド

struct MenuDetailView: View {
    @Bindable var menu: Menu
    
    var body: some View {
        Form{
            Section("メニュー"){
                TextField("名前", text: $menu.name)
                TextField("個数", text: $menu.amount)
            }
            Section("追加履歴"){
                Button("履歴追加",action: addMenu)
                ForEach(menu.menuLogs){ log in
                    Text(log.dateEntry.formatted(date: .abbreviated, time: .shortened))
                }
            }
        }
        .navigationTitle("項目詳細")
        .navigationBarTitleDisplayMode(.inline)
    }
    func addMenu() {        menu.menuLogs.append(FruitsLog(dateEntry: Date.now))
    }
}

#Preview {
    MenuDetailView(menu: Menu(name: "", amount: ""))
}

てな感じで、

てな感じ

で次にやっとこコンテンツビュー

相変わらず、本だけだと、説明が薄い+プレビューで実行もできないコードなので〜〜〜

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    var body: some View {
        NavigationStack{
            MenuListView()
                .navigationTitle("メニュー")
                .navigationDestination(for: Menu.self, destination: MenuDetailView.init).toolbar{
                    Button("新メニュー",systemImage: "plus",action: addMenu)
                }
        }
    }
    
    func addMenu(){
        let menu = Menu(name: "", amount: "")
        modelContext.insert(menu)
    }
}

#Preview {
    ContentView().modelContainer(for: Menu.self, inMemory: true)
}
てな感じ

ここまででプレビューで実行してみると

右上の+をタップ
>をタップ
てな感じで戻ると、
てな感じ

まではできたことを確認

後は、文字検索機能を追加

まずはContentsViewを

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @State private var searchName = ""

    var body: some View {
        NavigationStack{
            MenuListView(searchName:searchName)
                .navigationTitle("メニュー")
                .searchable(text: $searchName)
                .navigationDestination(for: Menu.self, destination: MenuDetailView.init).toolbar{
                    Button("新メニュー",systemImage: "plus",action: addMenu)
                }
        }
    }
    
    func addMenu(){
        let menu = Menu(name: "", amount: "")
        modelContext.insert(menu)
    }
}

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

てな感じに書き換えて、MenuListViewに対応するイニシャライザとクエリを追加〜〜〜

struct MenuListView: View {
    @Environment(\.modelContext) var modelContext
    @Query var fruitsMenu: [Menu]
    
    var body: some View {
        List{
            ForEach(fruitsMenu){ menu in
                NavigationLink(value: menu){
                    if(menu.amount.isEmpty){
                        Text("新しい項目を編集")
                            .foregroundStyle(Color.gray)
                    } else {
                        Text("\(menu.amount),\(menu.name)")
                    }
                }
            }
            .onDelete(perform: deleteMenu)
        }
    }
    
    init(searchName:String){
        _fruitsMenu = Query(filter: #Predicate{
            if searchName.isEmpty{
                return true
            } else {
                return $0.name.localizedStandardContains(searchName)
            }
        })
    }
    
    func deleteMenu(_ indexSet: IndexSet){
        for idx in indexSet {
            let menu = fruitsMenu[idx]
            modelContext.delete(menu)
        }
    }
}

#Preview {
    MenuListView(searchName: "")
}

プレビューで確認(完成形)

検索バーが追加されたけども、まだDBは空なので〜〜〜+をタップ
>をタップ
履歴追加をタップ
左上のメニューをタップ
検索バーに🍎を入力
てな感じで
でけた〜〜〜

シミュレーターで実行

てな
てな感じで

一回、実行し直して〜〜〜

ちゃんとデータ残ってんね😛

ほい、データベースとしても画面との連携も、永続性も問題なし。

今回のコードまとめ

◾️E53SwiftDataTutirialsApp.swift

import SwiftUI
import SwiftData

@main
struct E53SwiftDataTutirialsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for:Menu.self)
    }
}

◾️ContentView.swift

import SwiftUI
import SwiftData
import Foundation

@Model
class Menu {
    var name: String
    var amount: String
    
    @Relationship(deleteRule: .cascade) var menuLogs = [FruitsLog]()
    
    init(name: String, amount: String) {
        self.name = name
        self.amount = amount
    }
}

@Model
class FruitsLog {
    var dateEntry: Date
    init(dateEntry: Date) {
        self.dateEntry = dateEntry
    }
}

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @State private var searchName = ""

    var body: some View {
        NavigationStack{
            MenuListView(searchName:searchName)
                .navigationTitle("メニュー")
                .searchable(text: $searchName)
                .navigationDestination(for: Menu.self, destination: MenuDetailView.init).toolbar{
                    Button("新メニュー",systemImage: "plus",action: addMenu)
                }
        }
    }
    
    func addMenu(){
        let menu = Menu(name: "", amount: "")
        modelContext.insert(menu)
    }
}

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

struct MenuListView: View {
    @Environment(\.modelContext) var modelContext
    @Query var fruitsMenu: [Menu]
    
    var body: some View {
        List{
            ForEach(fruitsMenu){ menu in
                NavigationLink(value: menu){
                    if(menu.amount.isEmpty){
                        Text("新しい項目を編集")
                            .foregroundStyle(Color.gray)
                    } else {
                        Text("\(menu.amount),\(menu.name)")
                    }
                }
            }
            .onDelete(perform: deleteMenu)
        }
    }
    
    init(searchName:String){
        _fruitsMenu = Query(filter: #Predicate{
            if searchName.isEmpty{
                return true
            } else {
                return $0.name.localizedStandardContains(searchName)
            }
        })
    }
    
    func deleteMenu(_ indexSet: IndexSet){
        for idx in indexSet {
            let menu = fruitsMenu[idx]
            modelContext.delete(menu)
        }
    }
}

#Preview {
    MenuListView(searchName: "")
}

struct MenuDetailView: View {
    @Bindable var menu: Menu
    
    var body: some View {
        Form{
            Section("メニュー"){
                TextField("名前", text: $menu.name)
                TextField("個数", text: $menu.amount)
            }
            Section("追加履歴"){
                Button("履歴追加",action: addMenu)
                ForEach(menu.menuLogs){ log in
                    Text(log.dateEntry.formatted(date: .abbreviated, time: .shortened))
                }
            }
        }
        .navigationTitle("項目詳細")
        .navigationBarTitleDisplayMode(.inline)
    }
    func addMenu() {
        menu.menuLogs.append(FruitsLog(dateEntry: Date.now))
    }
}

#Preview {
    MenuDetailView(menu: Menu(name: "", amount: ""))
}

💃めっちゃシンプル🕺

Apple公式

さてと、次回は

冒頭に書いたとおり、今回で1年に及んだ、

SwiftUIの連載記事【じっくりSw1ftUI72】シリーズ
も実践編まで無事完了
🕺

以降は、

てな感じで、

Widgetキットが主な話=スマホアプリの付属機能=番外編

なので、、、

で書いてたとおり、今回で

💃本編は終了🕺
後は、気が向いた時にサラッと書くかな
🧐
くらいなもん
🤣

ではでは、ここまでのご愛読ありがとうございました。
基本的なアプリのビューとデータベースの連携まで含めて、SwiftUIでのアプリ開発の基本は全て網羅したつもりなので、後は、自分でこれまでの記事を参考に、自分なりのアプリを作ってみてね〜〜〜

次回は、冒頭で書いたとおり、ちょいコラで、
ここまでの振り返りとSwiftUIのDB周りの機能比較なぞ
を書きまする(余裕があれば、明日やも
😛

記事公開後、

今回も特になし。

この記事が気に入ったらサポートをしてみませんか?