1

I have some items and show an item count on the view. I have a toggle that reduces what is shown in the count/item list. The item count doesn't update when I use the toggle. What am I missing here? Cut down / mock example for conciseness.

struct SampleView: View {

    @State var items:[Item]
    @State var easyMode:Bool = false
    @State var filtered:[Item] = []

    init(...) {
        // Fill items and filtered array
    }

    var body: some View {

            Toggle(isOn: $easyMode, label:{
                Text("Easy mode")
            })
            .onChange(of: self.easyMode, perform: { value in
                filtered = filterItems()
                //         ^^ This is running but not
                //            updating stuff
            })
            // This text field does not update when filter changes
            Text(String(filtered.count) + " items")
    }
}

I tried having the filterItems generate a text string in i.e. @State var countLabel:String and update the text, but there is a background thread issue. So I assume there is some weirdness where the filterItems isn't doing anything because its on a separate thread. What am I missing? Thanks!!

Jay
  • 19,649
  • 38
  • 121
  • 184

2 Answers2

1

you could try this:

.onChange(of: self.easyMode) { value in
    DispatchQueue.main.async {
       filtered = filterItems()
    }
}
1

I have just take your code and make it compilable.

struct SOTest: View {
    @State var items:[String]
    @State var easyMode:Bool = false
    @State var filtered: [String]
    
    init() {
        let tmp = ["1","2_","3","4_","5_","6"]
        self.items = tmp
        self.filtered = tmp.filterStrings()
    }

    var body: some View {

            Toggle(isOn: $easyMode, label:{
                Text("Easy mode")
            })
            .onChange(of: self.easyMode, perform: { value in
                
                if value {
                    filtered = items.filterStrings()
                } else {
                    filtered = items
                }
            })
            Text(String(filtered.count) + " items")
    }
}

extension Array where Element == String {
    func filterStrings() -> [String] {
        self.filter{ $0.contains("_") }
    }
}

and all works perfectly.

Possibly you doing something wrong in your original code and in your question you lost something important.

Try to check my answer here, maybe you will find solution of your hidden issue: SwiftUI: Forcing an Update

and.... by the way, here is more simple way to do the same:

struct SOTest: View {
    @State var items:[String]
    @State var easyMode:Bool = false
    @State var filtered: [String]

    init() {
        let tmp = ["1","2_","3","4_","5_","6"]
        self.items = tmp
        self.filtered = tmp.filterStrings()
    }

    var body: some View {
        Toggle(isOn: $easyMode, label:{
            Text("Easy mode")
        })
        
        if easyMode {
            Text(String(filtered.count) + " items")
        }
        else {
            Text(String(items.count) + " items")
        }
    }
}
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
  • 1
    Wow! Thats amazing. hmmm, thats confusing, I spent an hour trying to work out why it wasn't working.... I am slowly copying and pasting code from the real project into the test project to work out where the problem is – Jay Jul 05 '21 at 14:04