1

I’m trying to pass a filter array to multiple views, but the filtering is not working. If I remove the filter, you can pass the array to the next view, but that leads to another error during the ForEach loop. I've posted all the code below.

Does anyone know how you can pass a filter version of a @Bindable array? Also why can't I print sport.name and sport.isFavorite.description in the ForEach loop?

I’m using swiftUI on Xcode 11.0 beta 5.

import SwiftUI
import Combine

struct Sport: Identifiable{
    var id = UUID()
    var name : String
    var isFavorite = false
}

final class SportData: ObservableObject  {
    @Published var store =
        [
            Sport(name: "soccer",   isFavorite: false),
            Sport(name: "tennis",   isFavorite: false),
            Sport(name: "swimming", isFavorite: true),
            Sport(name: "running",  isFavorite: true)
    ]
}

struct Testing: View {
    @ObservedObject var sports = SportData()

    var body: some View {
        VStack {
            TestingTwo(sports: $sports.store.filter({$0.isFavorite}))
        }
    }
}

struct TestingTwo: View {
    @Binding var sports : [Sport]

    var body: some View {t
        NavigationView {
            VStack(spacing: 10){
                ForEach($sports) { sport in
                    NavigationLink(destination: TestingThree(sport: sport)){
                        HStack {
                            Text(sport.name)
                            Spacer()
                            Text(sport.isFavorite.description)
                        }
                        .padding(.horizontal)
                        .frame(width: 200, height: 50)
                        .background(Color.blue)
                    }
                }
            }
        }
    }
}

struct TestingThree: View {
    @Binding var sport : Sport

    var body: some View {
        VStack {
            Text(sport.isFavorite.description)
                .onTapGesture {
                    self.sport.isFavorite.toggle()
            }
        }
    }
}


#if DEBUG
struct Testing_Previews: PreviewProvider {
    static var previews: some View {
        Testing()
    }
}
#endif
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Richard Witherspoon
  • 4,082
  • 3
  • 17
  • 33

2 Answers2

3

Filtering in your case might be better placed in the navigation view, due to your binding requirements.

struct Testing: View {
    @ObservedObject var sports = SportData()

    var body: some View {
        VStack {
            TestingTwo(sports: $sports.store)
        }
    }
}

struct TestingTwo: View {
    @Binding var sports : [Sport]
    @State var onlyFavorites = false

    var body: some View {t
        NavigationView {
            VStack(spacing: 10){
                ForEach($sports) { sport in
                    if !self.onlyFavorites || sport.value.isFavorite {
                        NavigationLink(destination: TestingThree(sport: sport)){
                            HStack {
                                Text(sport.value.name)
                                Spacer()
                                Text(sport.value.isFavorite.description)
                            }
                            .padding(.horizontal)
                            .frame(width: 200, height: 50)
                            .background(Color.blue)
                        }
                    }
                }
            }
        }
    }
}

Now you can switch the isFavorite state either within the action implementation of a button, or while specifying the integration of you TestingTwo view.

struct Testing: View {
    @ObservedObject var sports = SportData()

    var body: some View {
        VStack {
            TestingTwo(sports: $sports.store, onlyFavorites: true)
        }
    }
}

Regarding the second part of your question: Note the value addendum in the ForEach loop. You're dealing with as binding here (as ForEach($sports) indicates), hence sport is not an instance of Sport.

Maki
  • 469
  • 3
  • 6
  • This doesn't seem to keep TestingTwo as a very reusable view. I've come up with a much better example of the problem I'm trying to solve. It can be found here: https://stackoverflow.com/questions/57456832/filter-observable-variables-to-pass-to-reusable-views-swiftui – Richard Witherspoon Aug 12 '19 at 07:00
2

You can't get a @Binding from a computed property, since the computed property is computed dynamically. A typical way to avoid this is to pass in ids of the sports objects and the data store itself, whereby you can access the sports items via id from the store.

If you really want to pass a @Binding in you have to remove the filter (pass in an actually backed array) and modfy the ForEach like the following:

  ForEach($sports.store) { (sport: Binding<Sport>) in
Fabian
  • 5,040
  • 2
  • 23
  • 35
  • I came up with a better question for this topic. Would the same principle apply?: https://stackoverflow.com/questions/57456832/filter-observable-variables-to-pass-to-reusable-views-swiftui – Richard Witherspoon Aug 12 '19 at 08:31
  • 1
    I don't know about the other question, but what I can tell is that if you want to pass a binding in the source of truth cannot be dynamic, but rather must be stored inside an `ObservableObject`, as `State` somewhere or using the `.constant`-initializers. – Fabian Aug 13 '19 at 10:31