【じっくりSw1ftUI70】実践(DB)編39〜第51章 iOS 17 SwiftUI Core Data と CloudKit のチュートリアル〜今日でちょうど連載開始丸1年🧐感慨深いねえ
さてと、前回
で、Xcodeのアップグレードがてら
ちょいコラでお茶を濁さざるを得なかった
けど、約1ヶ月ぶりに
いよいよ本編へ〜〜〜〜
ま、毎度のことだけど、オイラの学びなんざ関係ないって人は
でも読めばいいんじゃね😛ま、全部動くサンプルが載ってるか自体知らんけど😆
んだば早速
じっくり第51章を読んでく👓
まずは概要として、タイトル通り
CoreDataと連携するために、
CloudKit
を使えるようにする設定方法について書いてんね👀💦
ちょっと、今回用の新しいプロジェクトを用意
Xcodeを立ち上げて
ここまでで前準備は出来たかな🧐
で本編だと、
おお👀他の設定とここまではおんなじ感じ💦
オイラは今年もAppleDeveloperライセンスは取得して、参加してるはず(うろ覚え😛)なのに、、、
と思うたら、銀行口座の内容がセキュリティ厳しくなったのか、有効な口座情報を登録して、今まで問題なかったのに、
👉契約更新のメールだけは先月来てたから更新したのに笑👀💦
うーむ🧐先週の、
iOS18へのアップデート
といい、この時期は本当にAppleとのライセンス契約で、
何かとバタバタすんね藁😆
ま、7年間何かとあったからすでに慣れっこではあるが💦
24時間後に反映で今30分くらい
今週もここで打ち止めかと思いきや〜〜〜
と、それでも
これだからApple Accountと連携させるサービスを利用するのは好きじゃない
ここでポイント①:Cloudサービスは有効なアカウントが前提なので
オイラはあんまりアプリを複雑にしたくないので、この時点でやっぱり
使わないなあCloudKit
👉まあ、管理がしっかりできれば便利なんだろうけど藁😆
後はすでに完全に消化試合な感じで、このまま続きがいけるかどうかを実験してみよう🕺
まあ、コンソールを追加しろてゆーてるので
てことは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からデータが確認できるみたいなので〜〜〜
と後はなんかこの画面の設定みたいだけど、説明も薄いし、指示どおり試そうとしても、結局、
今回はここまで〜〜〜🕺
💃後は、CloudKitを使いたい時に練習しよう🕺
Apple公式
さてと、次回は
オイラがこの書籍を学ぼうと思ったきっかけでもある
SwiftData
について触れた
第52章 SwiftData ガイド
についてやってく💃
恒例の記事公開後は、
前々回の記事でゆーたけど、
これまでのプロジェクトファイルの動きが遅くなってる
CloudKitとの連携は連携先のCloudKitが今回みたいに説明が薄いと上手く動かない
👉結合しても意味がないので今回もなし。
前回のちょいコラでゆーてたけど、
実践編もいよいよ残りラスト2回だねえ🧐
この連載を始めてから、
今日でちょうど丸1年
休み休みしっかり遊びながらやったり、
連休中に一気に数記事書いたりで、
テケトーにその場その場の気分でマイペースにやってきたけど、結局、
週に1記事ペース
で、そっちの方が継続できるもんだねえ。
💃じゃあ次回からもまたよろしくお願いします🕺