My SwiftUI code is becoming un-SwiftUI-y. I'm looking to make things right again.

I have a singleton instance of a class that (among other things) is managing which subset of words will be available to users.

The contents of availableWords will always be a subset of words and is always a function of three userDefaults that are bound to user settings (using @AppStorage)

I could dynamically reconstruct availableWords every time it is needed, but it will be read much more frequently than it changes. Because of this, I want to cache the updated list every time a user changes one of the settings that will change its contents.

But the only way I can see to do this is to create an update function and rely on the UI code to call the function any time a user updates one of the settings that will require availableWords to be updated. And this feels more like something out of UIKit.

Do any of you see a better way of managing the updates of availableWords?


class WordsManager {
    static let shared = WordsManager()
    
    let words: Words	// defined in init
    var availableWords: Words   // updated anytime scriptPickers, languageChoice or cardPile changes

    @AppStorage("scriptPickers") var scriptPickers:  ScriptPickers = ScriptPickers.defaultDictionary
    @AppStorage("languageChoice") var languageChoice: LanguageChoice = .all
    @AppStorage("cardPile") var cardPile: CardPile = .allRandom

    func updateAvailableWords() {
        var result = words.filtered(by: cardPile.wordList)
        let askIdentifiers = languageChoice.askLanguages
        let answerIdentifiers = languageChoice.answerLanguages
        result = result.matching(askIdentifiers: askIdentifiers, answerIdentifiers: answerIdentifiers)
        self.availableWords = result
    }

// other stuff
}

If I understand correctly, you could use .onChange modifier to update the content each time user updates one of the settings.

If you want more precise answer, please show the part of code where user updates settings.

You've got a property of your model which is complex to calculate (availableWords) and depends on two other properties (cardPile and languageChoice). That dependency has nothing to do with UI.

You can make a custom setter for cardPile and languageChoice which calls updateAvailableProperties. That satisfies the dependency, independent of UI code.

In the UI, bind to languageChoice and cardPile. That makes the intent clear. The connection between those two properties and changes to availableWords is only visible in WordsManager, your UI doesn't need to know about it.

Try using a didSet on each of the AppStorage fields. This can either:

  • immediately call updateAvailableWords whenever there's a change
  • set a flag indicating that availableWords is out of date; make availableWords a computed property that updates itself only if that flag is true

Also, I would make availableWords private(set) - external clients should not be manipulating it directly.

My SwiftUI code is becoming un-SwiftUI-y. I'm looking to make things right again.
 
 
Q