Search code examples
iosswiftdata-bindingswiftui

SwiftUI Binding to Computed Property?


I have two classes, one is the ContentView who is displaying the information from my data source. The data source is my CharacterRepository.

What I'm struggling with right now is making sure I always have a sorted list inside of my CharacterRepository.

Here's the code I have so far:

class CharacterRepository: ObservableObject {
    @Published public var characters = [Character(name: "Nott the Brave",
                                                  initiative: 23,
                                                  isActive: false),
                                        Character(name: "Caduceus Clay",
                                                  initiative: 2,
                                                  isActive: false),
                                        ...]
    ...
}

and

struct InitiativeTrackerScreen: View {
    @EnvironmentObject var characterRepository: CharacterRepository

    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(characterRepository.characters) { entry in
                        CharacterListElement(character: entry)
                    }
    ...

Now my intended approach would be something like turning the characters variable into a computed property that runs the "sorted by" function every time the get gets executed. Unfortunately, Binding to a computed property is not yet possible in SwiftUI (not sure, will it ever be?).

Can someone please help me with this? I don't want to go back to the old approach of sorting and redrawing every time something changes. That's not why I am using SwiftUI with ist sweet bindings.


Solution

  • I think it may be unnecessary overhead to be sorting the data in the property getter.

    The simple (and performance-conscious) solution is to sort the data when it changes, and then to update a @Published non-computed property and bind to that:

    class CharacterRepository: ObservableObject {
        func updateCharacters(_ characters: [Character]) {
           self.characters = characters.sorted() // or whatever sorting strategy you need...
        }
    
        @Published public var characters = [Character(name: "Nott the Brave",
                                                      initiative: 23,
                                                      isActive: false),
                                            Character(name: "Caduceus Clay",
                                                      initiative: 2,
                                                      isActive: false),
                                            ...]
        ...
    }
    

    If you prefer to be über-purist about this at the cost of performance, then you can manually create a Binding - something like this:

    class CharacterRepository: ObservableObject {
       let sortedCharacters: Binding<[Character]>
       ...
    
    
       init() { 
          self.sortedCharacters = .init(get: { return characters.sorted() }, set: { self.characters = $0 })
       }
    
       ...
    }