简体   繁体   中英

Can't get SwiftUI View objects to update from @Published ObservableObject or @EnvironmentObject variables

I've done a ton of searching and read a bunch of articles but I cannot get SwiftUI to dynamically update the view based on changing variables in the model, at least the kind of thing I'm doing. Basically I want to update the view based on the app's UNNotificationSettings.UNAuthorizationStatus . I have the app check the status on launch and display the status. If the status is not determined, then tapping on the text will trigger the request notifications dialog. However, the view doesn't update after the user either permits or denies the notifications. I'm sure I'm missing something fundamental because I've tried it a dozen ways, including with @Published ObservableObject , @ObservedObject , @EnvironmentObject , etc.

struct ContentView: View {
    @EnvironmentObject var theViewModel : TestViewModel
    
    var body: some View {
        VStack {
            Text(verbatim: "Notifications are: \(theViewModel.notificationSettings.authorizationStatus)")
                .padding()
        }
        .onTapGesture {
            if theViewModel.notificationSettings.authorizationStatus == .notDetermined {
                theViewModel.requestNotificationPermissions()
            }
        }
    }
}

class TestViewModel : ObservableObject {
    @Published var notificationSettings : UNNotificationSettings
    
    init() {
        notificationSettings = type(of:self).getNotificationSettings()!
    }
    
    
    func requestNotificationPermissions() {
        let permissionsToRequest : UNAuthorizationOptions = [.alert, .sound, .carPlay, .announcement, .badge]
        UNUserNotificationCenter.current().requestAuthorization(options: permissionsToRequest) { granted, error in
            if granted {
                print("notification request GRANTED")
            }
            else {
                print("notification request DENIED")
            }
            if let error = error {
                print("Error requesting notifications:\n\(error)")
            }
            else {
                DispatchQueue.main.sync {
                    self.notificationSettings = type(of:self).getNotificationSettings()!
                }
            }
        }
    }

    static func getNotificationSettings() -> UNNotificationSettings? {
        var settings : UNNotificationSettings?
        let start = Date()
        
        let semaphore = DispatchSemaphore(value: 0)
        UNUserNotificationCenter.current().getNotificationSettings { notificationSettings in
            settings = notificationSettings
            semaphore.signal()
        }
        semaphore.wait()
        
        while settings == nil {
            let elapsed = start.distance(to: Date())
            Thread.sleep(forTimeInterval: TimeInterval(0.001))
            if elapsed > TimeInterval(1) {
                print("ERROR: did not get notification settings in less than a second, giving up!")
                break
            }
        }
        if settings != nil {
            print("\(Date())  Notifications are: \(settings!.authorizationStatus)")
        }
            
        return settings
    }
}


func getUNAuthorizationStatusString(_ authStatus : UNAuthorizationStatus) -> String {
    switch authStatus {
    case .notDetermined:    return "not determined"
    case .denied:           return "denied"
    case .authorized:       return "authorized"
    case .provisional:      return "provisional"
    case .ephemeral:        return "ephemeral"
    @unknown default:       return "unknown case with rawValue \(authStatus.rawValue)"
    }
}


extension UNAuthorizationStatus : CustomStringConvertible {
    public var description: String {
        return getUNAuthorizationStatusString(self)
    }
}

extension String.StringInterpolation {
    mutating func appendInterpolation(_ authStatus: UNAuthorizationStatus) {
        appendLiteral(getUNAuthorizationStatusString(authStatus))
    }
}

EDIT: I tried adding objectWillChange but the view still isn't updating.

class TestViewModel : ObservableObject {
    let objectWillChange = ObservableObjectPublisher()
    
    @Published var notificationSettings : UNNotificationSettings {
        willSet {
            objectWillChange.send()
        }
    }
    
    
    init() {
        notificationSettings = type(of:self).getNotificationSettings()!
    }

Per the apple docs the properties wrappers like @Published should hold values. UNNotificationSettings is a reference type. Since the class gets mutated and the pointer never changes, @Publushed has no idea that you changed anything. Either publish a value (it make a struct and init it from he class) or manually send the objectwillChange message manually.

While I was not able to get it to work with manually using objectWillChange , I did create a basic working system as follows. Some functions are not repeated from the question above.

struct TestModel {
    var notificationAuthorizationStatus : UNAuthorizationStatus
    
    init() {
        notificationAuthorizationStatus = getNotificationSettings()!.authorizationStatus
    }
}

class TestViewModel : ObservableObject {
    @Published var theModel = TestModel()
    
    func requestAndUpdateNotificationStatus() {
        requestNotificationPermissions()
        theModel.notificationAuthorizationStatus = getNotificationSettings()!.authorizationStatus
    }
}

struct ContentView: View {
    @ObservedObject var theViewModel : TestViewModel
    
    var body: some View {
        VStack {
            Button("Tap to update") {
                theViewModel.requestAndUpdateNotificationStatus()
            }
            .padding()
            switch theViewModel.theModel.notificationAuthorizationStatus {
            case .notDetermined:    Text("Notifications have not been requested yet.")
            case .denied:           Text("Notifications are denied.")
            case .authorized:       Text("Notifications are authorized.")
            case .provisional:      Text("Notifications are provisional.")
            case .ephemeral:        Text("Notifications are ephemeral.")
            @unknown default:       Text("Notifications status is an unexpected state.")
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM