Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CloudKitTrial.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
40B3CEB72A03916300D733C4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 40B3CEB62A03916300D733C4 /* Assets.xcassets */; };
40B3CEBA2A03916300D733C4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 40B3CEB92A03916300D733C4 /* Preview Assets.xcassets */; };
40B3CEC12A0393D300D733C4 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 40B3CEC02A0393D300D733C4 /* .gitignore */; };
40B3CEC62A04D5C600D733C4 /* ItemListing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B3CEC52A04D5C600D733C4 /* ItemListing.swift */; };
40B3CEC92A04D82400D733C4 /* ItemListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B3CEC82A04D82400D733C4 /* ItemListViewModel.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -22,6 +24,9 @@
40B3CEB92A03916300D733C4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
40B3CEC02A0393D300D733C4 /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
40B3CEC22A0394EA00D733C4 /* CloudKitTrial.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CloudKitTrial.entitlements; sourceTree = "<group>"; };
40B3CEC32A04D3ED00D733C4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
40B3CEC52A04D5C600D733C4 /* ItemListing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListing.swift; sourceTree = "<group>"; };
40B3CEC82A04D82400D733C4 /* ItemListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListViewModel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -55,6 +60,9 @@
40B3CEB12A03916200D733C4 /* CloudKitTrial */ = {
isa = PBXGroup;
children = (
40B3CEC72A04D80700D733C4 /* ViewModel */,
40B3CEC42A04D59800D733C4 /* Model */,
40B3CEC32A04D3ED00D733C4 /* Info.plist */,
40B3CEC22A0394EA00D733C4 /* CloudKitTrial.entitlements */,
40B3CEB22A03916200D733C4 /* CloudKitTrialApp.swift */,
40B3CEB42A03916200D733C4 /* ContentView.swift */,
Expand All @@ -72,6 +80,22 @@
path = "Preview Content";
sourceTree = "<group>";
};
40B3CEC42A04D59800D733C4 /* Model */ = {
isa = PBXGroup;
children = (
40B3CEC52A04D5C600D733C4 /* ItemListing.swift */,
);
path = Model;
sourceTree = "<group>";
};
40B3CEC72A04D80700D733C4 /* ViewModel */ = {
isa = PBXGroup;
children = (
40B3CEC82A04D82400D733C4 /* ItemListViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -144,6 +168,8 @@
buildActionMask = 2147483647;
files = (
40B3CEB52A03916200D733C4 /* ContentView.swift in Sources */,
40B3CEC92A04D82400D733C4 /* ItemListViewModel.swift in Sources */,
40B3CEC62A04D5C600D733C4 /* ItemListing.swift in Sources */,
40B3CEB32A03916200D733C4 /* CloudKitTrialApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -277,6 +303,7 @@
DEVELOPMENT_TEAM = M8JXU75248;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = CloudKitTrial/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down Expand Up @@ -307,6 +334,7 @@
DEVELOPMENT_TEAM = M8JXU75248;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = CloudKitTrial/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down
2 changes: 2 additions & 0 deletions CloudKitTrial/CloudKitTrial.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@
<array>
<string>CloudKit</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
</dict>
</plist>
7 changes: 6 additions & 1 deletion CloudKitTrial/CloudKitTrialApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
//

import SwiftUI
import CloudKit

@main
struct CloudKitTrialApp: App {

// public container
let container = CKContainer(identifier: "iCloud.com.example.cookcook")

var body: some Scene {
WindowGroup {
ContentView()
ContentView(vm: ItemListViewModel(container: container))
}
}
}
70 changes: 63 additions & 7 deletions CloudKitTrial/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,77 @@
//

import SwiftUI
import CloudKit

struct ContentView: View {

@StateObject private var vm: ItemListViewModel

@State var title: String = ""
@State var price: String = ""

init(vm: ItemListViewModel) {
_vm = StateObject(wrappedValue: vm)
}

func deleteItem(_ indexSet: IndexSet) {
indexSet.forEach { index in
let item = vm.items[index]
if let recordId = item.recordId {
vm.deleteItem(recordId)
}

}
}

var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")

NavigationView {

VStack {

TextField("Enter Ingredient", text: $title)
.textFieldStyle(.roundedBorder)
TextField("Enter Price", text: $price)
.textFieldStyle(.roundedBorder)

Button("Save") {
guard let price = try? Decimal(price, format: .number) else { return }

vm.saveItem(title: title, price: price)

self.title = ""
self.price = ""

}.disabled(title.trimmingCharacters(in: .whitespaces).isEmpty || price.trimmingCharacters(in: .whitespaces).isEmpty)

Spacer()

List {
ForEach(vm.items, id: \.recordId) { item in
HStack {
Text(item.title)
Spacer()
Text("$ " + String(describing: item.price))
}
}.onDelete(perform: deleteItem)
}


.navigationTitle("Ingredient")
}
.padding()
.onAppear {
vm.populateItem()
}
}
.padding()


}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
ContentView(vm: ItemListViewModel(container: CKContainer.default()))
}
}
34 changes: 34 additions & 0 deletions CloudKitTrial/Model/ItemListing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// ItemListing.swift
// CloudKitTrial
//
// Created by JaeUngJang on 2023/05/05.
//

import Foundation
import CloudKit

struct ItemListing {

var recordId: CKRecord.ID?
let title: String
let price: Decimal

init(recordId: CKRecord.ID? = nil, title: String, price: Decimal) {
self.recordId = recordId
self.title = title
self.price = price
}

func toDictionary() -> [String: Any] {
return ["title": title, "price": price]
}

static func fromRecord(_ record: CKRecord) -> ItemListing? {
guard let title = record.value(forKey: "title") as? String , let price = record.value(forKey: "price") as? Double else {
return nil
}

return ItemListing(recordId: record.recordID, title: title, price: Decimal(price))
}
}
109 changes: 109 additions & 0 deletions CloudKitTrial/ViewModel/ItemListViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// ItemListViewModel.swift
// CloudKitTrial
//
// Created by JaeUngJang on 2023/05/05.
//

import Foundation
import CloudKit

enum RecordType: String {
case itemListing = "ItemListing"
}

class ItemListViewModel: ObservableObject {

private var database: CKDatabase
private var container: CKContainer

@Published var items: [ItemViewModel] = []

init(container: CKContainer) {
self.container = container
self.database = self.container.publicCloudDatabase
}

func deleteItem(_ recordId: CKRecord.ID) {
database.delete(withRecordID: recordId) { deletedRecordId, error in
if let error = error {
print("Error")
} else {
self.populateItem()
}

}

}

func saveItem(title: String, price: Decimal) {

let record = CKRecord(recordType: RecordType.itemListing.rawValue)
let itemListing = ItemListing(title: title, price: price)
record.setValuesForKeys(itemListing.toDictionary())

// saving record in database
self.database.save(record) { newRecord, error in
if let error = error {
print(error)
} else {
if let newRecord = newRecord {
if let itemListing = ItemListing.fromRecord(newRecord) {
DispatchQueue.main.async {
self.items.append(ItemViewModel(itemListing: itemListing))
}
}
print("SAVED")
}
}
}
}

func populateItem() {

var items: [ItemListing] = []

let query = CKQuery(recordType: RecordType.itemListing.rawValue, predicate: NSPredicate(value: true))

database.fetch(withQuery: query) { result in
switch result {
case .success(let result):
result.matchResults.compactMap { $0.1 }
.forEach {
switch $0 {
case .success(let record):
if let itemListing = ItemListing.fromRecord(record) {
items.append(itemListing)
}
// print(record)
case .failure(let error):
print(error)
}
}

DispatchQueue.main.async {
self.items = items.map(ItemViewModel.init)
}
// print(result.matchResults)
case .failure(let error):
print(error)
}
}
}
}

struct ItemViewModel {
let itemListing: ItemListing

var recordId: CKRecord.ID? {
itemListing.recordId
}

var title: String {
itemListing.title
}

var price: Decimal {
itemListing.price
}
}