icloudhttps://icloud.developer.apple.com/dashboard/database/teams/7QEL77G33U/containers/iCloud.com.swiftful.SwiftfulThinkingAdvancedLearning
import Foundation
import CloudKit
struct CloudKitFruitModelNames{
static let fruits = "Fruits"
static let name = "name"
static let image = "image"
static let count = "count"
}
struct FruitModel: Hashable, CloudKitableProtocol{
let name: String
let imageURL: URL?
let count: Int
let record: CKRecord
// 初始化 没有名字,意味着失败
init?(record: CKRecord) {
guard let name = record[CloudKitFruitModelNames.name] as? String else { return nil }
self.name = name
// 获取图片
let imageAsset = record[CloudKitFruitModelNames.image] as? CKAsset
self.imageURL = imageAsset?.fileURL
let count = record[CloudKitFruitModelNames.count] as? Int
self.count = count ?? 0
self.record = record
}
init?(name: String, imageURL: URL?, count: Int?){
let record = CKRecord(recordType: CloudKitFruitModelNames.fruits)
record[CloudKitFruitModelNames.name] = name
if let url = imageURL{
let asset = CKAsset(fileURL: url)
//设置路径
record[CloudKitFruitModelNames.image] = asset
}
if let count = count {
record[CloudKitFruitModelNames.count] = count
}
self.init(record: record)
}
// 更新
func update(newName: String) -> FruitModel?{
let record = record
record[CloudKitFruitModelNames.name] = newName
return FruitModel(record: record)
}
}
import Foundation
import CloudKit
import Combine
import UserNotifications
/// 云套件可信协议
protocol CloudKitableProtocol {
init?(record: CKRecord)
var record: CKRecord { get }
}
/// 云套件的实用工具
class CloudKitUtility {
/// 错误类型
enum CloudKitError: String, LocalizedError{
case iCloudAccountNotFound
case iCloudAccountNotDetermined
case iCloudAccountRestricted
case iCloudAccountUnknown
case iCloudApplicationPermissionNotGranted
case iCloudCouldNotFetchUserRecordID
case iCloudCouldNotDiscoverUser
case iCloudNotificationPermmissionFailure
}
}
// MARK: USER FUNCTIONS
extension CloudKitUtility{
/// 获取状态,跟尾随闭包
static private func getiCloudStatus(completion: @escaping (Result) -> ()){
// 添加容器
CKContainer.default().accountStatus { returnedStatus, returnedError in
switch returnedStatus{
case .available:
// 可用的
completion(.success(true))
case .noAccount:
// 无账号
completion(.failure(CloudKitError.iCloudAccountNotFound))
case .couldNotDetermine:
// 无法确定
completion(.failure(CloudKitError.iCloudAccountNotDetermined))
case .restricted:
// 设限制
completion(.failure(CloudKitError.iCloudAccountRestricted))
default:
completion(.failure(CloudKitError.iCloudAccountUnknown))
}
}
}
/// Future:获取状态,定义未来值返回数据
static func getiCloudStatus() -> Future{
Future { promise in
getiCloudStatus { result in
promise(result)
}
}
}
/// 请求应用权限
static private func requestApplicationPermission(completion: @escaping(Result) -> ()){
CKContainer.default().requestApplicationPermission([.userDiscoverability]) { returnedPermissionStatus, returnedError in
// 授权状态为同意
if returnedPermissionStatus == .granted {
completion(.success(true))
}else{
completion(.failure(CloudKitError.iCloudApplicationPermissionNotGranted))
}
}
}
/// Future:获取请求应用权限,定义未来值返回数据
static func requestApplicationPermission() -> Future{
Future { promise in
requestApplicationPermission { result in
promise(result)
}
}
}
/// 获取用户记录ID
static private func fetchUserRecordID(completion: @escaping(Result) -> ()){
CKContainer.default().fetchUserRecordID { returnedRecordID, returnedError in
if let id = returnedRecordID {
completion(.success(id))
} else if let error = returnedError {
completion(.failure(error))
} else{
completion(.failure(CloudKitError.iCloudCouldNotFetchUserRecordID))
}
}
}
/// 根据用户记录ID,获取用户信息
static private func discoverUserIdentity(id: CKRecord.ID, completion: @escaping(Result) -> ()){
CKContainer.default().discoverUserIdentity(withUserRecordID: id) { returnedIdentity, returnedError in
if let name = returnedIdentity?.nameComponents?.givenName {
completion(.success(name))
// print(returnedIdentity?.lookupInfo?.userRecordID?.recordName ?? "")
}else if let error = returnedError{
completion(.failure(error))
}else{
completion(.failure(CloudKitError.iCloudCouldNotDiscoverUser))
}
}
}
/// 获取用户记录ID,根据用户记录ID,获取用户信息
static private func discoverUserIdentity(completion: @escaping(Result) -> ()){
fetchUserRecordID { fetchCompletion in
switch fetchCompletion {
case .success(let recordID):
discoverUserIdentity(id: recordID, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
}
/// Future:获取用户信息,定义未来值返回值
static func discoverUserIdentity() -> Future{
Future { promiss in
discoverUserIdentity { result in
promiss(result)
}
}
}
}
// MARK: CRUD FUNCTIONS
extension CloudKitUtility{
///Future : 查询数据,定义未来值返回数据
static func fetch(
predicate: NSPredicate,
recordType: CKRecord.RecordType,
sortDescriptors: [NSSortDescriptor]? = nil,
resultsLimit: Int? = nil
) -> Future <[T], Error>{
Future { promise in
fetch(predicate: predicate, recordType: recordType, sortDescriptors: sortDescriptors, resultsLimit: resultsLimit) { items in
promise(.success(items))
}
}
}
/// 查询数据
static private func fetch(
predicate: NSPredicate,
recordType: CKRecord.RecordType,
sortDescriptors: [NSSortDescriptor]? = nil,
resultsLimit: Int? = nil,
completion: @escaping (_ items: [T]) -> ()
){
// 创建查询操作
let operation = createOperation(predicate: predicate, recordType: recordType, sortDescriptors: sortDescriptors, resultsLimit: resultsLimit)
// 查询获取项目组 items in query
var returnedItems: [T] = []
// 添加记录匹配块
addRecordMatchedBlock(operation: operation) { item in
returnedItems.append(item)
}
// 查询完成: Query completion
addQueryResultBlock(operation: operation) { finished in
completion(returnedItems)
}
// 添加操作: Execute operation
add(operation: operation)
}
/// 创建操作
static private func createOperation(
predicate: NSPredicate,
recordType: CKRecord.RecordType,
sortDescriptors: [NSSortDescriptor]? = nil,
resultsLimit: Int? = nil) -> CKQueryOperation{
// 查找类型
let query = CKQuery(recordType: recordType, predicate: predicate)
// 排序 升序 name: 名称 creationDate: 创建日期
// query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
query.sortDescriptors = sortDescriptors
// 设置操作查询
let queryOperation = CKQueryOperation(query: query)
// 查询结果限制 返回前多少个, 限制最大100
if let limit = resultsLimit{
queryOperation.resultsLimit = limit
}
return queryOperation
}
/// 添加记录匹配块
static private func addRecordMatchedBlock(operation: CKQueryOperation, completion: @escaping (_ item: T) -> ()){
// 记录匹配返回
if #available(iOS 15.0, *) {
operation.recordMatchedBlock = { (returnedRecordID, returnedRecordResult) in
switch returnedRecordResult{
case .success(let record):
guard let item = T(record: record) else { return }
completion(item)
case .failure(let error):
print("Error recordMatchedBlock: \(error)")
break
}
}
} else {
operation.recordFetchedBlock = { returnedRecord in
guard let item = T(record: returnedRecord) else { return }
completion(item)
}
}
}
/// 添加查询结果块
static private func addQueryResultBlock(operation: CKQueryOperation, completion: @escaping (_ finished: Bool) -> ()){
if #available(iOS 15.0, *) {
operation.queryResultBlock = {returnedResult in
// returnedResult 返回结果带有游标,表示返回多少条数据,根据游标查询剩下的数据
//print("RETURNED queryResultBlock: \(returnedResult)")
completion(true)
}
} else {
// iOS 15 之前查询
operation.queryCompletionBlock = {(returnedCursor, returnedError) in
// let cursor = returnedCursor == nil ? "" : returnedCursor?.description ?? ""
// print("RETURNED queryCompletionBlock: \(cursor)")
//self?.fruits = returnedItems
completion(true)
}
}
}
/// 添加操作
static private func add(operation: CKDatabaseOperation){
CKContainer.default().publicCloudDatabase.add(operation)
}
/// Future: 添加数据,定义未来值返回数据
static func add(item: T) -> Future{
Future { promise in
add(item: item, completion: promise)
}
}
/// 添加项
static private func add(item: T, completion: @escaping (Result) -> ()){
// 保存到云套件
save(record: item.record, completion: completion)
}
/// Future: 更新数据,定义未来值返回数据
static func update(item: T) -> Future{
Future { promise in
update(item: item, completion: promise)
}
}
/// 更新项
static private func update(item: T, completion: @escaping (Result) -> ()){
// 获取记录 保存到云套件
save(record: item.record, completion: completion)
}
/// 保存数据
static private func save(record: CKRecord, completion: @escaping (Result) -> ()){
// 使用公共云数据库进行存储
CKContainer.default().publicCloudDatabase.save(record) { returnedRecord, returnedError in
if let error = returnedError {
//print("Error: \(returnedError?.localizedDescription ?? "")")
completion(.failure(error))
}else {
//print("Record: \(returnedRecord?.description ?? "")")
completion(.success(true))
}
}
}
/// Future: 删除数据,定义未来值返回数据
static func delete(item: T) -> Future{
// promise: 履行一个承诺
Future { promise in
delete(item: item, completion: promise)
}
}
/// 删除数据
static private func delete(item: T, completion: @escaping (Result ) -> ()){
// 删除
delete(record: item.record, completion: completion)
}
/// 删除数据
static private func delete(record: CKRecord, completion: @escaping (Result) -> ()){
CKContainer.default().publicCloudDatabase.delete(withRecordID: record.recordID) { returnedRecordID, returnedError in
if let error = returnedError{
completion(.failure(error))
}else{
completion(.success(true))
}
}
}
}
// MARK: PUSH FUNCTIONS
extension CloudKitUtility{
/// Future: 请求权限,定义未来值返回数据
static func requestAuthorization(options: UNAuthorizationOptions) -> Future{
// promise: 履行一个承诺
Future { promise in
requestAuthorization(options: options, completion: promise)
}
}
/// 请求权限
static private func requestAuthorization(options: UNAuthorizationOptions, completion: @escaping (Result) -> ()){
UNUserNotificationCenter.current().requestAuthorization(options: options) { success, error in
if let error = error {
completion(.failure(error))
}else if success{
completion(.success(success))
}else {
completion(.failure(CloudKitError.iCloudNotificationPermmissionFailure))
}
}
}
/// Future: 保存订阅通知到云套件中,定义未来值返回数据
static func save(subscription: CKSubscription) -> Future{
// promise: 履行一个承诺
Future { promise in
save(subscription: subscription, completion: promise)
}
}
/// 保存订阅通知到云套件中
static private func save(subscription: CKSubscription, completion: @escaping (Result) -> ()){
CKContainer.default().publicCloudDatabase.save(subscription) { returnedSubscription, returnedError in
if let error = returnedError {
completion(.failure(error))
}else {
let subscriptionID = returnedSubscription?.subscriptionID ?? ""
completion(.success(subscriptionID))
}
}
}
/// 创建查询订阅通知
static func createSubscribeToNotifications(
predicate: NSPredicate,
recordType: CKRecord.RecordType,
subscriptionID: CKSubscription.ID,
options: CKQuerySubscription.Options = [.firesOnRecordCreation],
notificationInfo: CKSubscription.NotificationInfo
) -> CKQuerySubscription{
// 查询创建订阅
let subscription = CKQuerySubscription(
recordType: recordType,
predicate: predicate,
subscriptionID: subscriptionID,
options: options)
// 订阅通知
subscription.notificationInfo = notificationInfo
return subscription
}
/// 创建通知
static func createNotificationInfo(
title: String?,
alertBody: String?,
soundName: String?
) -> CKSubscription.NotificationInfo{
// 通知信息
let notification = CKSubscription.NotificationInfo()
notification.title = title
notification.alertBody = alertBody
notification.soundName = soundName
return notification
}
/// Future: 删除云套件中的通知,定义未来值返回数据
static func delete(subscriptionID: CKSubscription.ID) -> Future{
// promise: 履行一个承诺
Future { promise in
delete(subscriptionID: subscriptionID, completion: promise)
}
}
/// 删除云套件中的通知
static private func delete(subscriptionID: CKSubscription.ID, completion: @escaping (Result) -> ()){
// 查询所有的订阅ID
// CKContainer.default().publicCloudDatabase.fetchAllSubscriptions
// 删除
CKContainer.default().publicCloudDatabase.delete(withSubscriptionID: subscriptionID) { returnedID, returnedError in
if let error = returnedError {
completion(.failure(error))
}else{
completion(.success(returnedID ?? ""))
}
}
}
}
必须在设置中登录 iCloud 账号,否则提示失败 "Not Authenticated" (9); "No iCloud account is configured"
import SwiftUI
import Combine
class CloudKitUserBootcampViewModel: ObservableObject{
@Published var permissionStatus: Bool = false
@Published var isSignedInToiCloud: Bool = false
@Published var error: String = ""
@Published var userName: String = ""
var cancellables = Set()
init() {
getiCloudStatus()
requestPermission()
getCurrentUserName()
}
/// 获取状态
private func getiCloudStatus(){
// 添加容器
// CKContainer.default().accountStatus { [weak self]returnedStatus, returnedError in
// DispatchQueue.main.async {
// switch returnedStatus{
// case .available:
// // 可用的
// self?.isSignedInToiCloud = true
// case .noAccount:
// // 无账号
// self?.error = CloudKitError.iCloudAccountNotFound.rawValue
// case .couldNotDetermine:
// // 无法确定
// self?.error = CloudKitError.iCloudAccountNotDetermined.rawValue
// case .restricted:
// // 设限制
// self?.error = CloudKitError.iCloudAccountRestricted.rawValue
// default:
// self?.error = CloudKitError.iCloudAccountUnknown.rawValue
// }
// }
// }
// 添加容器 封装云套件的状态
// CloudKitUtility.getiCloudStatus { [weak self] completion in
// DispatchQueue.main.async {
// switch completion{
// case .success(let bool):
// self?.isSignedInToiCloud = bool
// case .failure(let error):
// self?.error = error.localizedDescription
// }
// }
// }
/// 使用定义未来值返回数据
CloudKitUtility.getiCloudStatus()
.receive(on: DispatchQueue.main)
.sink { [weak self]completion in
switch completion {
case .finished:
break
case .failure(let error):
self?.error = error.localizedDescription
}
} receiveValue: { [weak self] success in
self?.isSignedInToiCloud = success
}
.store(in: &cancellables)
}
// 请求权限,查看用户信息
func requestPermission(){
// CKContainer.default().requestApplicationPermission([.userDiscoverability]) { [weak self] returnedPermissionStatus, returnedError in
// DispatchQueue.main.async {
// // 状态为授权
// if returnedPermissionStatus == .granted {
// self?.permissionStatus = true
// }
// }
// }
// 使用定义未来值返回数据
CloudKitUtility.requestApplicationPermission()
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
case .finished:
break
case .failure(let error):
print("Error: \(error)")
}
} receiveValue: { [weak self] success in
self?.permissionStatus = success
}
.store(in: &cancellables)
}
/// 获取当前的用户名称
func getCurrentUserName(){
CloudKitUtility.discoverUserIdentity()
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion{
case .finished:
break
case .failure(let error):
print("Error: \(error)")
}
} receiveValue: { [weak self] returnedName in
self?.userName = returnedName
}
.store(in: &cancellables)
}
}
// 云套件用户信息
struct CloudKitUserBootcamp: View {
@StateObject private var viewModel = CloudKitUserBootcampViewModel()
var body: some View {
VStack {
Text("IS SIGNED IN: \(viewModel.isSignedInToiCloud.description.uppercased())")
Text(viewModel.error)
Text("Permission: \(viewModel.permissionStatus.description)")
Text("")
Text("NAME: \(viewModel.userName)")
}
}
}
struct CloudKitUserBootcamp_Previews: PreviewProvider {
static var previews: some View {
CloudKitUserBootcamp()
}
}
import SwiftUI
import Combine
class CloudKitCrudBootcampViewModel: ObservableObject{
@Published var text:String = ""
@Published var fruits:[FruitModel] = []
var cancellables = Set()
init() {
fetchItems()
}
/// 添加按钮点击
func addButtonPressed(){
guard !text.isEmpty else { return }
addItem(name: text)
}
/// 添加数据项
private func addItem(name :String){
//添加上传图片
guard
let image = UIImage(named: "therock"),
let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("therock.jpg"),
let data = image.jpegData(compressionQuality: 1.0) else { return }
do {
try data.write(to: url)
//获取路径
guard let newFruit = FruitModel(name: name, imageURL: url, count: 6) else { return }
// CloudKitUtility.add(item: newFruit) { [weak self] result in
// DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// self?.text = ""
// self?.fetchItems()
// }
// }
// .delay(for: 2, scheduler: DispatchQueue.main)
CloudKitUtility.add(item: newFruit)
// 接收数据之后,延时 2 秒,然后在主线程上执行
.delay(for: 2, scheduler: DispatchQueue.main)
.sink { _ in
} receiveValue: { [weak self] success in
self?.text = ""
self?.fetchItems()
}
.store(in: &cancellables)
} catch let error {
print("Error: \(error)")
}
}
/// 查询数据项
private func fetchItems(){
// 断定数据
let predicate = NSPredicate(value: true)
// 自定义查询数据 名称的对应
//let predicate = NSPredicate(format: "name = %@", argumentArray: ["Coconut"])
// 查询数据
// CloudKitUtility.fetch(predicate: predicate, recordType: "Fruits") { [weak self] returnedItems in
// DispatchQueue.main.async {
// self?.fruits = returnedItems
// }
// }
let recordType = "Fruits"
CloudKitUtility.fetch(predicate:predicate, recordType: recordType)
.receive(on: DispatchQueue.main)
.sink { _ in
} receiveValue: { [weak self] returnedItems in
self?.fruits = returnedItems
}
.store(in: &cancellables)
}
/// 更新数据项
func updateItem(fruit: FruitModel){
guard let fruit = fruit.update(newName: "NEW NAME!!!") else { return }
// CloudKitUtility.update(item: fruit) { [weak self] result in
// print("UPDATE COMPLETED")
// self?.fetchItems()
// }
CloudKitUtility.update(item: fruit)
.receive(on: DispatchQueue.main)
.sink { _ in
} receiveValue: { [weak self] success in
print("UPDATE COMPLETED")
self?.fetchItems()
}
.store(in: &cancellables)
}
/// 删除数据项
func deleteItem(indexSet: IndexSet){
// 判断是否为空
guard let index = indexSet.first else { return }
let fruit = fruits[index]
//let record = fruit.record
//删除操作
// CKContainer.default().publicCloudDatabase.delete(withRecordID: record.recordID) { [weak self] returnedRecordID, returnedError in
// DispatchQueue.main.async {
// self?.fruits.remove(at: index)
// }
CloudKitUtility.delete(item: fruit)
.receive(on: DispatchQueue.main)
.sink { _ in
} receiveValue: {[weak self] success in
print("DELETE IS: \(success)")
self?.fruits.remove(at: index)
}
.store(in: &cancellables)
}
}
/// 云套件增删改查
struct CloudKitCrudBootcamp: View {
@StateObject private var viewModel = CloudKitCrudBootcampViewModel()
var body: some View {
NavigationView{
VStack {
header
textField
addButton
listView
}
.padding()
}
}
}
struct CloudKitCrudBootcamp_Previews: PreviewProvider {
static var previews: some View {
CloudKitCrudBootcamp()
}
}
extension CloudKitCrudBootcamp {
/// 头部分
private var header: some View{
Text("CloudKit CRUD ☁️☁️☁️")
.font(.title)
.underline()
}
/// 输入文本
private var textField: some View{
TextField("Add some here...", text: $viewModel.text)
.frame(height: 55)
.padding(.horizontal)
.background(Color.gray.opacity(0.4))
.cornerRadius(10)
}
/// 添加按钮
private var addButton: some View{
Button {
viewModel.addButtonPressed()
} label: {
Text("Add")
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.orange)
.cornerRadius(10)
}
}
/// 添加列表
private var listView: some View{
List {
ForEach(viewModel.fruits, id: \.self) { fruit in
HStack {
Text(fruit.name)
// 判断是否为空,根据图片地址显示图片
if let url = fruit.imageURL, let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
Image(uiImage: image)
.resizable()
.frame(width: 50, height: 50)
}
}
.onTapGesture {
viewModel.updateItem(fruit: fruit)
}
}
.onDelete(perform: viewModel.deleteItem)
}
.listStyle(.plain)
}
}
云套件推送通知需要两台设备,云套件数据操作的设备模拟器和真机都可行,云套件推送通知得运行在真机下,一台设备上使用 云套件增删改查 添加数据操作,另一台设备上订阅推送通知
云套件推送通知页面里授权完成,点击订阅通知,退到后台,才能监听到推送通知,需要保持账号一致并登录到 iCloud 云
import SwiftUI
import Combine
// protocol: 协议
// generics: 泛型
// futures: 未来的值
class CloudKitPushNotificationBootcampViewModel: ObservableObject{
private let subscriptionID = "fruit_added_to_database"
private let recordType = "Fruits"
/// 随时取消
var cancellables = Set()
/// 请求通知
func requestNotificationPermissions(){
// UNUserNotificationCenter.current().requestAuthorization(options: options) { success, error in
// if let error = error {
// print("Error: \(error)")
// }else if success{
// print("Notification permmission success.")
// DispatchQueue.main.async {
// UIApplication.shared.registerForRemoteNotifications()
// }
// }else {
// print("Notification permmission failure.")
// }
// }
// 请求授权 警告 声音 徽章
let options: UNAuthorizationOptions = [.alert, .sound, .badge]
CloudKitUtility.requestAuthorization(options: options)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion{
case .finished:
break
case .failure(let error):
print(error.localizedDescription)
}
} receiveValue: { success in
print("Notification permmission success.")
UIApplication.shared.registerForRemoteNotifications()
}
.store(in: &cancellables)
}
/// 订阅通知(其他设备操作云套件数据,将监听到通知)
func subscribeToNotifications(){
// let predicate = NSPredicate(value: true)
// 查询订阅 创建的时候得到一个通知
// let subscription = CKQuerySubscription(recordType: "Fruits", predicate: predicate, subscriptionID: subscriptionID, options: .firesOnRecordCreation)
// 通知信息
// let notification = CKSubscription.NotificationInfo()
// notification.title = "There's a new fruit!"
// notification.alertBody = "Open the app to check your fruits."
// notification.soundName = "default"
// 设置通知
// subscription.notificationInfo = notification
// 保存到云套件 数据库中
// CKContainer.default().publicCloudDatabase.save(subscription) { returnedSubscription, returnedError in
// if let error = returnedError {
// print("Error: \(error)")
// }else {
// print("Successfully subscribad to notifications.")
// }
// }
// 创建通知信息
let notificationInfo = CloudKitUtility.createNotificationInfo(
title: "There's a new fruit!",
alertBody: "Open the app to check your fruits.",
soundName: "default")
// 创建一个判定
let predicate = NSPredicate(value: true)
// 创建查询订阅通知
let subscription = CloudKitUtility.createSubscribeToNotifications(
predicate: predicate,
recordType: recordType,
subscriptionID: subscriptionID,
notificationInfo: notificationInfo)
// 保存订阅通知到云套件中
CloudKitUtility.save(subscription: subscription)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion{
case .finished:
break
case .failure(let error):
print("Error: \(error)")
}
} receiveValue: { returnedID in
print("Successfully subscribad to notifications: \(returnedID)")
}
.store(in: &cancellables)
}
/// 取消订阅
func unsubscribeToNotifications(){
// CKContainer.default().publicCloudDatabase.delete(withSubscriptionID: subscriptionID) { returnID, returnError in
// if let error = returnError {
// print("Error: \(error)")
// }else{
// print("Successfully unsubscribad.")
// }
// }
// 删除云套件中的订阅通知
CloudKitUtility.delete(subscriptionID: subscriptionID)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion{
case .finished:
break
case .failure(let error):
print("Error: \(error)")
}
} receiveValue: { returnedID in
print("Successfully unsubscribad: \(returnedID)")
}
.store(in: &cancellables)
}
}
/// 云套件推送通知
struct CloudKitPushNotificationBootcamp: View {
@StateObject private var viewModel = CloudKitPushNotificationBootcampViewModel()
var body: some View {
VStack(spacing: 40) {
// 请求通知权限
Button("Request notification permission") {
viewModel.requestNotificationPermissions()
}
// 订阅通知
Button("Subscribe to notifications") {
viewModel.subscribeToNotifications()
}
// 取消订阅
Button("UnSubscribe to notifications") {
viewModel.unsubscribeToNotifications()
}
}
}
}
struct CloudKitPushNotificationBootcamp_Previews: PreviewProvider {
static var previews: some View {
CloudKitPushNotificationBootcamp()
}
}