CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用

1. 项目添加支持云套件库

  1.1 使用云套件服务,需要注册 Apple ID 账号,并且开通 Apple 开发者账号

  1.2 选中项目 -> TARGETS 下的项目 -> Signing & Capabilities -> +Capability,弹出对话框如下:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第1张图片

  1.3 添加 iCloud 后,选择键值和云套件库等,如图:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第2张图片

  1.4 云套件网址:

icloudicon-default.png?t=N7T8https://icloud.developer.apple.com/dashboard/database/teams/7QEL77G33U/containers/iCloud.com.swiftful.SwiftfulThinkingAdvancedLearning

  1.5 添加数据表如图:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第3张图片

  1.6 数据表中添加的数据如图:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第4张图片

2. 创建数据模型 Model,FruitModel.swift

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)
    }
}

3. 封装云套件工具类 CloudKitUtility.swift

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 ?? ""))
            }
        }
    }
}

4. 获取云套件用户信息

  4.1 登录 iCloud 账号

    必须在设置中登录 iCloud 账号,否则提示失败 "Not Authenticated" (9); "No iCloud account is configured"

  4.2 创建 View,CloudKitUserBootcamp.swift

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()
    }
}

  4.3 效果图:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第5张图片

5. 云套件的增删改查数据,添加 Apple Orange/Watermelon 信息

  5.1 创建实现 View,CloudKitCrudBootcamp.swift

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)
    }
}

  5.2 效果图:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第6张图片

6. 云套件推送通知

  6.1 简述

    云套件推送通知需要两台设备,云套件数据操作的设备模拟器和真机都可行,云套件推送通知得运行在真机下,一台设备上使用 云套件增删改查 添加数据操作,另一台设备上订阅推送通知

    云套件推送通知页面里授权完成,点击订阅通知,退到后台,才能监听到推送通知,需要保持账号一致并登录到 iCloud 云

  6.2 添加云套件推送通知库,与添加云套件方法一致,选择库不同,如图:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第7张图片

  6.3 创建实现云套件推送通知 View CloudKitPushNotificationBootcamp.swift

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()
    }
}

  6.4 请求权限图:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第8张图片    CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第9张图片

  6.5 订阅通知图:

CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第10张图片    CloudKitUser/云套件用户信息, CloudKitCrud/云套件增删改查, CloudKitPushNotification/云套件推送通知 的使用_第11张图片

你可能感兴趣的:(SwiftUI,Advanced,Learning,iOS,Swift,UI)