There are many possible variants of this question, but take as an example the CNAuthorizationStatus returned by CNContactStore.authorizationStatus(for: .contacts), which can be notDetermined, restricted, denied, or authorized. My goal is to always show the current authorization status in my app's UI.
To expose this to SwiftUI, I might make an ObservableObject called ModelData with a contacts property:
final class ModelData: ObservableObject {
@Published var contacts = Contacts.shared
}
Where contacts contains my contact-specific model code, including Authorization:
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
}
}
}
And I might add a method that a button could call to request access:
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)
}
}
}
And for the sake of simplicity, say my view is just:
Text(String(describing: modelData.contacts.authorization))
So my questions are:
- Given that
ModelData().contacts.authorizationcalls a getter function, not a property, how can I inform the SwiftUI view when I know it's changed (e.g. where the TODO is in therequestAccess()function)? - Given that the user can toggle the permission in the Settings app (i.e., the value might change out from under me), how can I ensure the view state is always updated? (Do I need to subscribe to an NSNotification and similarly force a refresh? Or is there a better way?)
As @jnpdx pointed out - using
@Publishedwith a class (especially a singleton that never changes) is probably not going to yield any useful results@Publishedbehaves likeCurrentValueSubjectand it will trigger an update only in case there are changes in the value it is storing/observing under the hood. Since it is storing a reference to theContacts.sharedinstance, it won't provide/trigger any updates for the authorization state changes.Now to your question - Given that
ModelData().contacts.authorizationcalls a getter function, not a property, how can I inform the SwiftUI view when I know it's changedAs long as you are directly accessing a value out of the getter
ModelData().contacts.authorization, it's just a value ofContacts.Authorizationtype that does NOT provide any observability.So even if the value changes over time (from
.notDetermined=>.authorized), there is no storage (reference point) against which we can compare whether it has changed since last time or not.We HAVE TO define a storage that can compare the old/new values and trigger updates as needed. This can achieved be by marking
authorizationas@Publishedlike following -