Home > Software design >  Swift array binding filtering - Conformance of Binding<Value> to Sequence
Swift array binding filtering - Conformance of Binding<Value> to Sequence

Time:06-14

I'm using the swift package [Defaults][1] to manage a list of preferences in my app. One of this property is an array of structures that I get using @Default(.list) var list. In Swift UI, I loop on this list to edit the various elements.

@Default(.list) var list;

ForEach($list, id: \.wrappedValue.id) { element in 
...
}

It's working fine and it works as expected.

My problem is that I need to filter this list. I'm using $list.filter(...) but I get a warning Conformance of 'Binding<Value>' to 'Sequence' is only available in MacOS 12.0.
Unfortunately, my app needs to run on MacOS 11.x.

I don't really understand what the warning means and how I can adapt my code to make it works with MacOS 11.x.

Thanks!

-- Update --

struct Stock: Codable, Identifiable, DefaultsSerializable {
   var id: String
   var currency: String = "USD"
}

extension Defaults.Keys {
    static let stocks = Key<[Stock]>("stocks", default: [])
}

struct StocksView: View {
    @Default(.stocks) var stocks

    func filterStocks(stock: Stock) -> Bool
    {
        return stock.currency == "USD"
    }

    var body: some View {
        ForEach($stocks.filter(filterStocks, id: \.wrappedValue.id) { stock in 
....
        }
    }
}


extension Binding where Value == [Stock] {//where String is your type
    func filter(_ condition: @escaping (Stock) -> Bool) -> Binding<Value> {//where String is your type
        return Binding {
            return wrappedValue.filter({condition($0)})
        } set: { newValue in
            wrappedValue = newValue
        }
    }
}







  [1]: https://github.com/sindresorhus/Defaults

CodePudding user response:

When you use $list.filter your are not filtering list you're filtering Binding<[List]> which doesn't conform to the protocol Sequence.

Add this extension to your project and see if it works

extension Binding where Value == [String] {//where String is your type
    func filterB(_ condition: @escaping (String) -> Bool) -> Binding<Value> {//where String is your type
        return Binding {
            return wrappedValue.filter({condition($0)})
        } set: { newValue in
            wrappedValue = newValue
        }
    }
}

Edit: Binding in a ForEach requires SwiftUI 3

However, you can pass the normal array and whenever you need a Binding use this function:

extension Stock {
    func binding(_ array: Binding<[Stock]>) -> Binding<Stock> {
        Binding {
            self
        } set: { newValue in
            guard let index = array.wrappedValue.firstIndex(where: {$0.id == newValue.id}) else {return}
            array.wrappedValue[index] = newValue
        }
    }
}

So your code would look like this:

ForEach(stocks.filter(filterStocks), id: \.id) { stock in
        ....
    SomeViewRequiringBinding(value: stock.binding(stocks))
}
  • Related