Jay
Jay

Reputation: 20136

How trigger state change / redraw on toggle change in SwiftUI

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!!

Upvotes: 1

Views: 1085

Answers (2)

Andrew_STOP_RU_WAR_IN_UA
Andrew_STOP_RU_WAR_IN_UA

Reputation: 11426

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")
        }
    }
}

Upvotes: 1

you could try this:

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

Upvotes: 1

Related Questions