如何在 SwiftUI 中引用外部 iOS 系统状态更新?

问题描述

这个问题有很多可能的变体,但以CNAuthorizationStatus返回的CNContactStore.authorizationStatus(for: .contacts)为例,可以是notDeterminedrestricted、{{1} },或denied。我的目标是始终在我的应用界面中显示当前的授权状态。

为了将其公开给 SwiftUI,我可能会使用 authorized 属性创建一个名为 ObservableObjectModelData

contacts

其中 final class ModelData: ObservableObject { @Published var contacts = Contacts.shared } 包含我的联系人特定型号代码包括授权:

contacts

我可能会添加一个按钮可以调用以请求访问的方法

class Contacts {
    fileprivate let store = CNContactStore()
    static let shared = Contacts()

    enum Authorization {
        case notDetermined
        case restricted
        case denied
        case authorized
    }
    
    var authorization: Authorization {
        switch CNContactStore.authorizationStatus(for: .contacts) {
        case .notDetermined:
            return .notDetermined
        case .restricted:
            return .restricted
        case .denied:
            return .denied
        case .authorized:
            return .authorized
        @unkNown default:
            return .notDetermined
        }
    }
}

为了简单起见,我的观点只是:

    func requestAccess(handler: @escaping (Bool,Error?) -> Void) {
        store.requestAccess(for: .contacts) { (granted,error) in
            // Todo: tell SwiftUI views to re-check authorization 
            
            dispatchQueue.main.async {
                handler(granted,error)
            }
        }
    }

所以我的问题是:

  1. 鉴于 Text(String(describing: modelData.contacts.authorization)) 调用的是 getter 函数,而不是属性,当我知道它已更改(例如,TodoModelData().contacts.authorization 函数中的位置)时,如何通知 SwiftUI 视图?
  2. 鉴于用户可以在“设置”应用中切换权限(即,值可能会从我下面更改),我如何确保视图状态始终更新? (我是否需要订阅 NSNotification 并类似地强制刷新?或者有更好的方法吗?)

解决方法

正如@jnpdx 指出的那样 - @Published 与类(尤其是从不改变的单例)一起使用可能不会产生任何有用的结果

@Published 的行为与 CurrentValueSubject 类似,只有在它在后台存储/观察的值发生变化时才会触发更新。由于它存储对 Contacts.shared 实例的引用,因此不会为授权状态更改提供/触发任何更新。

现在回答你的问题—— 鉴于 ModelData().contacts.authorization 调用的是 getter 函数,而不是属性,当我知道它已更改时如何通知 SwiftUI 视图

只要您直接访问 getter ModelData().contacts.authorization 之外的值,它只是一个不提供任何可观察性的 Contacts.Authorization 类型的值。

因此,即使值随时间变化(从 .notDetermined => .authorized),也没有存储(参考点)可以用来比较它自上次以来是否发生变化。

我们必须定义一个可以比较旧/新值并根据需要触发更新的存储。这可以通过将 authorization 标记为 @Published 来实现,如下所示 -

import SwiftUI
import Contacts

final class Contacts: ObservableObject {
    fileprivate let store = CNContactStore()
    static let shared = Contacts()
    
    enum Authorization {
        case notDetermined
        case restricted
        case denied
        case authorized
    }
    
    /// Since we have a storage (and hence a way to compare old/new status values)
    /// Anytime a new ( != old ) value is assigned to this
    /// It triggers `.send()` which triggers an update
    @Published var authorization: Authorization = .notDetermined
    
    init() {
        self.refreshAuthorizationStatus()
    }
    
    private func refreshAuthorizationStatus() {
        authorization = self.currentAuthorization()
    }
    
    private func currentAuthorization() -> Authorization {
        switch CNContactStore.authorizationStatus(for: .contacts) {
        case .notDetermined:
            return .notDetermined
        case .restricted:
            return .restricted
        case .denied:
            return .denied
        case .authorized:
            return .authorized
        @unknown default:
            return .notDetermined
        }
    }
    
    func requestAccess() {
        store.requestAccess(for: .contacts) { [weak self] (granted,error) in
            DispatchQueue.main.async {
                self?.refreshAuthorizationStatus()
            }
        }
    }
    
}

struct ContentView: View {
    @ObservedObject var contacts = Contacts.shared
    
    var body: some View {
        VStack(spacing: 16) {
            Text(String(describing: contacts.authorization))
            
            if contacts.authorization == .notDetermined {
                Button("Request Access",action: {
                    contacts.requestAccess()
                })
            }
        }
    }
}
,

我认为您已经完成了所有工作。 当用户从“设置”应用更改访问级别时,会调用此行。

Text(String(describing: modelData.contacts.authorization))

因此您的视图始终显示当前状态。