見出し画像

【じっくりSw1ftUI70】実践(DB)編39〜第51章 iOS 17 SwiftUI Core Data と CloudKit のチュートリアル〜今日でちょうど連載開始丸1年🧐感慨深いねえ

さてと、前回

で、Xcodeのアップグレードがてら

ちょいコラでお茶を濁さざるを得なかった

けど、約1ヶ月ぶりに

いよいよ本編へ〜〜〜〜

ま、毎度のことだけど、オイラの学びなんざ関係ないって人は

でも読めばいいんじゃね😛ま、全部動くサンプルが載ってるか自体知らんけど😆

んだば早速


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

まずは概要として、タイトル通り

CoreDataと連携するために、

CloudKit

を使えるようにする設定方法について書いてんね👀💦

ちょっと、今回用の新しいプロジェクトを用意

Xcodeを立ち上げて

CreateNewProjectをクリック


名前をこんな感じにして〜〜〜
てな感じにして、Createボタンで先に進むと〜〜
てな感じで、新規プロジェクトが開いた🕺

ここまでで前準備は出来たかな🧐

で本編だと、

コイツをクリック
青くなってる箇所をクリック
青くなってるコイツが確実に開いてるかを確認
Allの右横をクリック

おお👀他の設定とここまではおんなじ感じ💦

出てこないね

オイラは今年もAppleDeveloperライセンスは取得して、参加してるはず(うろ覚え😛)なのに、、、
と思うたら、銀行口座の内容がセキュリティ厳しくなったのか、有効な口座情報を登録して、今まで問題なかったのに、

てな感じで、契約が更新できてなかったのね藁😆

👉契約更新のメールだけは先月来てたから更新したのに笑👀💦

うーむ🧐先週の、

iOS18へのアップデート

といい、この時期は本当にAppleとのライセンス契約で、

何かとバタバタすんね藁😆
ま、7年間何かとあったからすでに慣れっこではあるが💦

24時間後に反映で今30分くらい

今週もここで打ち止めかと思いきや〜〜〜

さっきの表示が消えて登録できたっぽい
24時間は一体、、、

と、それでも

変わらず表示されね〜〜〜😛

これだからApple Accountと連携させるサービスを利用するのは好きじゃない

ここでポイント①:Cloudサービスは有効なアカウントが前提なので

オイラはあんまりアプリを複雑にしたくないので、この時点でやっぱり

使わないなあCloudKit
👉まあ、管理がしっかりできれば便利なんだろうけど藁😆

後はすでに完全に消化試合な感じで、このまま続きがいけるかどうかを実験してみよう🕺

まあ、コンソールを追加しろてゆーてるので

ここの+をクリック
てな感じで、今回用のコンテナを追加〜〜〜
Capabilityにはなかったのに追加されたよ藁😛

てことはiCloudは有効に利用できるのね💦👀
ここまで悪戦苦闘しながらすでに1時間以上経過🧐

作業コスト高えな🙄

で次はバックグラウンドのNotificationを有効に

てゆーてんだけど

立て付けでチェック入ってんね👀😛

まあ、ここは確認程度か

CloudKit用のコンテナをコードで設定

なんだけど、冒頭のサイト記事を見ると

.
.
let container: NSPersistentCloudKitContainer
.
.
init() {
    container = NSPersistentCloudKitContainer(name: "Products")
 
    container.loadPersistentStores { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Container load failed: \(error)")
        }
    }
}
init() {
    container = NSPersistentCloudKitContainer(name: "Products")
 
    container.loadPersistentStores { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Container load failed: \(error)")
        }
    }
    container.viewContext.automaticallyMergesChangesFromParent = true
}

相変わらずの、

分かってる前提😛
(まあ、日本の市販本に比べればマシだが😆)

なので、ちょいと

サンプルファイルを使って、コードなんかを編集

編集前(初期値)

◾️E51CloudKitTutorialApp.swift

import SwiftUI

@main
struct E51CloudKitTutorialApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

◾️Persistence.swift

import CoreData

struct PersistenceController {
    static let shared = PersistenceController()

    @MainActor
    static let preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
        }
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentCloudKitContainer

    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "E51CloudKitTutorial")
        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)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

◾️ContentView.swift

import SwiftUI
import CoreData

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

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>

    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        Text("Item at \(item.timestamp!, formatter: itemFormatter)")
                    } label: {
                        Text(item.timestamp!, formatter: itemFormatter)
                    }
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
            Text("Select an item")
        }
    }

    private func addItem() {
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()

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

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)

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

private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .medium
    return formatter
}()

#Preview {
    ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}

◾️E51CloudKitTutorial.xcdatamodeld

タイムスタンプのみ

編集後

◾️E51CloudKitTutorial.xcdatamodeld

てな

◾️E51CloudKitTutorialApp.swift

import SwiftUI

@main
struct E51CloudKitTutorialApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

◾️Persistence.swift

import CoreData

struct PersistenceController {
    static let shared = PersistenceController()
    
    let container: NSPersistentCloudKitContainer

    init() {
        container = NSPersistentCloudKitContainer(name: "E51CloudKitTutorial")
        
        container.loadPersistentStores { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Container load failed: \(error)")
            }
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

◾️ContentView.swift

import SwiftUI
import CoreData

struct ContentView: View {
    
    @State var menu: String = ""
    @State var amount: String = ""
    
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(key: "menu", ascending: true)])
    private var items: FetchedResults<Item>
    
    var body: some View {
        NavigationStack {
            VStack {
                TextField("商品名", text: $menu)
                TextField("個数", text: $amount)
                
                HStack {
                    Spacer()
                    Button("追加") {
                        addItem()
                    }
                    Spacer()
                    NavigationLink(destination: ResultsView(menu: menu,
                                           viewContext: viewContext)) {
                                Text("検索")
                    }
                    Spacer()

                    Button("クリア") {
                        menu = ""
                        amount = ""
                    }
                    Spacer()
                }
                .padding()
                .frame(maxWidth: .infinity)
                
                List {
                    ForEach(items) { item in
                        HStack {
                            Text(item.menu ?? "見つかりませんでした")
                            Spacer()
                            Text(item.amount ?? "見つかりませんでした")
                        }
                    }
                    .onDelete(perform: deleteItem)
                }
                .navigationTitle("商品データベース")
            }
            .padding()
            .textFieldStyle(RoundedBorderTextFieldStyle())
        }
    }

    private func addItem() {
        withAnimation {
            let item = Item(context: viewContext)
            item.menu = menu
            item.amount = amount
            
            saveContext()
        }
    }
    
    private func deleteItem(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)
                saveContext()
            }
    }
    
    private func saveContext() {
        do {
            try viewContext.save()
        } catch {
            let error = error as NSError
            fatalError("エラーが発生しました: \(error)")
        }
    }

}

struct ResultsView: View {
    
    var menu: String
    var viewContext: NSManagedObjectContext
    @State var matches: [Item]?

    var body: some View {
       
        return VStack {
            List {
                ForEach(matches ?? []) { match in
                    HStack {
                        Text(match.menu ?? "見つかりませんでした")
                        Spacer()
                        Text(match.amount ?? "見つかりませんでした")
                    }
                }
            }
            .navigationTitle("結果")
        }
        .task {
                let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
                
                fetchRequest.entity = Item.entity()
                fetchRequest.predicate = NSPredicate(
                    format: "menu CONTAINS %@", menu
                )
                matches = try? viewContext.fetch(fetchRequest)
            }
    }
}

#Preview {
    ContentView()
}

これをあのコードだけで表現て、
記述うっす笑😆

実行してみると〜〜〜〜

正常に動いたね👀

後いくつか追加してみて

てな感じで
で検索ボタンをタップ
でけた

CloudKitConsoleからデータが確認できるみたいなので〜〜〜

最初に触ってたコイツをクリック
Safariからログインして、コイツが出てきた👀💦
なんかかっちょいい画面が出てきたぞ👀

と後はなんかこの画面の設定みたいだけど、説明も薄いし、指示どおり試そうとしても、結局、

ここがデフォルト以外選択肢がないので

今回はここまで〜〜〜🕺
💃後は、CloudKitを使いたい時に練習しよう🕺

Apple公式

さてと、次回は

オイラがこの書籍を学ぼうと思ったきっかけでもある

SwiftData

について触れた

第52章 SwiftData ガイド

についてやってく💃

恒例の記事公開後は、

前々回の記事でゆーたけど、
これまでのプロジェクトファイルの動きが遅くなってる
CloudKitとの連携は連携先のCloudKitが今回みたいに説明が薄いと上手く動かない

👉結合しても意味がないので今回もなし。

前回のちょいコラでゆーてたけど、

実践編もいよいよ残りラスト2回だねえ🧐
この連載を始めてから、

今日でちょうど丸1年

休み休みしっかり遊びながらやったり、
連休中に一気に数記事書いたりで、
テケトーにその場その場の気分でマイペースにやってきたけど、結局、

週に1記事ペース

で、そっちの方が継続できるもんだねえ。

💃じゃあ次回からもまたよろしくお願いします🕺

いいなと思ったら応援しよう!