如何在 SwiftUI 中引用外部 iOS 系统状态更新?
How to reference external iOS system state updates in SwiftUI?
这道题有很多种可能的变体,但以CNContactStore.authorizationStatus(for: .contacts)
返回的CNAuthorizationStatus
为例,可以是notDetermined
、restricted
、denied
,或 authorized
。我的目标是始终在我的应用的 UI.
中显示当前授权状态
为了将其暴露给 SwiftUI,我可能会创建一个名为 ModelData
的 ObservableObject
和一个 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)
}
}
}
为了简单起见,我的观点是:
Text(String(describing: modelData.contacts.authorization))
所以我的问题是:
- 鉴于
ModelData().contacts.authorization
调用了 getter 函数,而不是 属性,当我知道 SwiftUI 视图发生变化时(例如,在哪里TODO 在 requestAccess()
函数中)?
- 鉴于用户可以在“设置”应用中切换权限(即,值可能会从我下面改变),我如何确保视图状态始终更新? (我是否需要订阅 NSNotification 并同样强制刷新?或者有更好的方法吗?)
正如@jnpdx 指出的那样 - 将 @Published
与 class(尤其是从不更改的单例)一起使用可能不会产生任何有用的结果
@Published
的行为类似于 CurrentValueSubject
,并且仅当其 storing/observing 的值发生变化时才会触发更新。由于它正在存储对 Contacts.shared
实例的引用,因此它不会 provide/trigger 授权状态更改的任何更新。
现在回答你的问题 -
鉴于 ModelData().contacts.authorization
调用了一个 getter 函数,而不是 属性,当我知道它已更改时如何通知 SwiftUI 视图
只要您直接访问 getter ModelData().contacts.authorization
之外的值,它只是 Contacts.Authorization
类型的值,不提供任何可观察性。
因此,即使值随时间变化(从 .notDetermined
=> .authorized
),也没有存储(参考点)可以用来比较它自上次以来是否发生了变化,或者没有。
我们必须定义一个可以比较 old/new 值并根据需要触发更新的存储。这可以通过将 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))
因此您的视图始终显示当前状态。
这道题有很多种可能的变体,但以CNContactStore.authorizationStatus(for: .contacts)
返回的CNAuthorizationStatus
为例,可以是notDetermined
、restricted
、denied
,或 authorized
。我的目标是始终在我的应用的 UI.
为了将其暴露给 SwiftUI,我可能会创建一个名为 ModelData
的 ObservableObject
和一个 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)
}
}
}
为了简单起见,我的观点是:
Text(String(describing: modelData.contacts.authorization))
所以我的问题是:
- 鉴于
ModelData().contacts.authorization
调用了 getter 函数,而不是 属性,当我知道 SwiftUI 视图发生变化时(例如,在哪里TODO 在requestAccess()
函数中)? - 鉴于用户可以在“设置”应用中切换权限(即,值可能会从我下面改变),我如何确保视图状态始终更新? (我是否需要订阅 NSNotification 并同样强制刷新?或者有更好的方法吗?)
正如@jnpdx 指出的那样 - 将 @Published
与 class(尤其是从不更改的单例)一起使用可能不会产生任何有用的结果
@Published
的行为类似于 CurrentValueSubject
,并且仅当其 storing/observing 的值发生变化时才会触发更新。由于它正在存储对 Contacts.shared
实例的引用,因此它不会 provide/trigger 授权状态更改的任何更新。
现在回答你的问题 -
鉴于 ModelData().contacts.authorization
调用了一个 getter 函数,而不是 属性,当我知道它已更改时如何通知 SwiftUI 视图
只要您直接访问 getter ModelData().contacts.authorization
之外的值,它只是 Contacts.Authorization
类型的值,不提供任何可观察性。
因此,即使值随时间变化(从 .notDetermined
=> .authorized
),也没有存储(参考点)可以用来比较它自上次以来是否发生了变化,或者没有。
我们必须定义一个可以比较 old/new 值并根据需要触发更新的存储。这可以通过将 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))
因此您的视图始终显示当前状态。