1

I am working on an iOS app which has a watchOS app as well. I have a settings bundle so when some settings is changed in the iPhone's settings application, the userdefaults of the app will be changed. And whenever a userdefault is updated, I need to send that to the watch app through watch connectivity.

For sending it to watch, I would need to know if a userdefault value is updated. I went through a lot of articles but nothing seems to be working out in my case.

The property wrapper @AppStorage is good if I need to listen for a userdefault and update a view. But thats not my case. I don't need to update a view. I need to know the change in userdefault and pass it on to watch.

I created a swift class with the following code,

import Foundation
import Combine

class UserDefaultsHelper: ObservableObject {
@Published var settings1: String = UserDefaults.standard.settings1  {
    didSet {
        UserDefaults.standard.settings1 = settings1
    }
}
@Published var settings2: String = UserDefaults.standard.settings2  {
    didSet {
        UserDefaults.standard.settings2 = settings2
    }
}

private var cancellable: AnyCancellable?
init() {
    cancellable = UserDefaults.standard.publisher(for: \.settings1)
        .sink(receiveValue: { [weak self] newValue in
            guard let self = self else { return }
            if newValue != self.settings1 { // avoid cycling !!
                self.settings1 = newValue
            }
        })
    
    cancellable = UserDefaults.standard.publisher(for: \.settings2)
        .sink(receiveValue: { [weak self] newValue in
            guard let self = self else { return }
            if newValue != self.settings2 { // avoid cycling !!
                self.settings2 = newValue
            }
        })
}
}

// define key for observing
extension UserDefaults {
@objc dynamic var settings1: String {
    get { string(forKey: "settings1") ?? "DEFAULT_VALUE" }
    set { setValue(newValue, forKey: "settings1") }
}

@objc dynamic var settings2: String {
    get { string(forKey: "settings2") ?? "DEFAULT_VALUE" }
    set { setValue(newValue, forKey: "settings2") }
}
}

But the problem is, I am not sure where should i call this class from. If I call from the content view, I get the error

Static method 'buildBlock' requires that 'UserDefaultsHelper' conform to 'View'.

So what is the right way to do this. The requirement is to observe the userdefaults and then a call a method in the WatchConnectivity class to pass it on to the watch.

Mano
  • 670
  • 1
  • 9
  • 28
  • The UserDefaults publisher is basically right. But you don't need the `@Published` properties nor the conformance to `ObservableObject`. You could change `class` to `actor` and pass the instance of `WatchConnectivity` in the `init` method. Then when the publisher `sink`s call the method in `WatchConnectivity`. By the way in the `init` method you overwrite `cancellable` so it contains only the `settings2` publisher. – vadian Nov 17 '22 at 17:07
  • I dont understand by what you mean "You could change class to actor and pass the instance of WatchConnectivity in init. Do you mind adding a code snippet pl? – Mano Nov 17 '22 at 18:32

0 Answers0