-1

I am trying to make a view that updates based on if the user toggles the favorite button or not. I want the entire view to reconstruct in order to display an array of values whenever that array of values is changed. Inside the view, a for each loop should display every value in the array.

The view that I want to update every time savedArray is changed is FavView. But when I try to use a foreach loop to display every value is savedArray(which I created as a @Published so the view would reconstruct), it gives me the error Generic struct 'ForEach' requires that 'Published<[String]>.Publisher' conform to 'RandomAccessCollection'. I am confused because I thought that String arrays were able to be used in for each loops. Is this not true? How do I loop through a @Published array? Thank you! This is my code for the savedArray(in ViewModel) and the FavView I want to display it in with the for each.

struct ContentView: View {
    @StateObject private var statNavManager = StatsNavigationManager()

    @State private var saved: [String] = []

    var body: some View {
        TabView {
                    
            StatsView(saved: $saved)
            .tabItem {
                Label("Home", systemImage: "house")
            }

            FavView(saved: $saved)
                .tabItem {
                    Label("Saved", systemImage: "bookmark")
            }
        }
        .environmentObject(statNavManager)
    }
}

final class ViewModel: ObservableObject {
        @Published var items = [Item]()
        @Published var showingFavs = true
        @Published var savedItems: Set<String> = []
        @Published var savedArray: [String]
        // Filter saved items
    
        var filteredItems: [String]  {
            //return self.items
            return savedArray
        }

        var db = Database()
        
        init() {
            self.savedItems = db.load()
            self.items = db.returnList()//the items
            self.savedArray = Array(db.load())
            print("savedarray", savedArray)
            print("important!", self.savedItems, self.items)
        }
        
        func contains(_ item: Item) -> Bool {
                savedItems.contains(item.id)
            }
        
        // Toggle saved items
        func toggleFav(item: Item) {
            print("Toggled!", item)
            if contains(item) {
                savedItems.remove(item.id)
                if let index = savedArray.firstIndex(of: item.id) {
                    savedArray.remove(at: index)
                }
            } else {
                savedItems.insert(item.id)
                savedArray.append(item.id)
            }
            db.save(items: savedItems)
        }
    }


struct FavView: View {
    @StateObject private var vm = ViewModel()

    var body: some View {
        VStack {
            
            List {
                var x = print("testing",vm.savedArray)//this only prints once at the start
                ForEach($vm.savedArray, id: \.self) { string in
                    let item = vm.db.returnItem(input: string.wrappedValue)
                    HStack {
                        VStack(alignment: .leading) {
                            Text(item.title)
                                .font(.headline)
                            
                            Text(item.description)
                                .font(.subheadline)
                        }
                        Spacer()
                        Image(systemName: vm.contains(item) ? "bookmark.fill" : "bookmark")
                            .foregroundColor(.blue)
                            .onTapGesture {
                                vm.toggleFav(item: item)
                            }
                    }
                }
            }
            .cornerRadius(10)
        }
    }
}


Linda Xue
  • 21
  • 4

1 Answers1

2

in ForEach, you are using $ symbol to access savedArray you have to use the vm itself

struct FavView: View {
    @StateObject private var vm = ViewModel()
    
    var body: some View {
        VStack {
            
            List {
  
                ForEach($vm.savedArray, id: \.self) { string in //< here $vm.savedArray not vm.$savedArray
                    let item = vm.db.returnItem(input: string)
                    HStack {
                        VStack(alignment: .leading) {
                            Text(item.title)
                                .font(.headline)
                            
                            Text(item.description)
                                .font(.subheadline)
                        }
                        Spacer()
                        Image(systemName: vm.contains(item) ? "bookmark.fill" : "bookmark")
                            .foregroundColor(.blue)
                            .onTapGesture {
                                vm.toggleFav(item: item)
                            }
                    }
                }
            }
            .cornerRadius(10)
        }
    }
}

this should work.

grandsirr
  • 584
  • 4
  • 19
Wahid Tariq
  • 159
  • 10
  • I just changed it and it gives me the error "Cannot convert value of type 'Binding' to expected argument type 'String'" on the line vm.db.returnItem(), which accepts a String input. I have edited to include this function. Thank you! – Linda Xue Oct 31 '22 at 12:21
  • if 'Published<[String]>.Publisher' conform to 'RandomAccessCollection'. is fixed please accept my answer – Wahid Tariq Oct 31 '22 at 12:40
  • share the screenshot of the code with the error so that it will be clear – Wahid Tariq Oct 31 '22 at 12:46
  • I fixed the problem by using .wrappedValue, so my code now runs, but it seems like the real reason my code does not work is because even though savedArray is changed, FavView does not re initialize when it is changed so it thus displays the old values. When I restart the program, FavView reconstructs. Is there a way to reconstruct FavView whenever I edit savedArray? Thanks! – Linda Xue Oct 31 '22 at 21:19