John Citrowske
John Citrowske

Reputation: 81

SwiftUI how to prevent view from automatically exiting when parent views list filters the item out

Hi I am fairly new to SwiftUI and I am building an application that has a list of items that, when clicked, show details about that item. There is an option to favorite these items and with a segmented picker at the top it filters the items to 'All items' or 'Favorites'. The problem is when the picker is on favorites, the user selects an item, and unfavorites the item in it's detail view, the user is automatically kicked out of it's detail view. Is there a simple way to prevent this?

Here is the code in the ContentView

import SwiftUI
struct ContentView: View {
    
    @EnvironmentObject var vm : ViewModel
    @State private var BiasStruct: BiasData = BiasData.allBias
    @State var searchText = ""
    @State var filterSearchText = ""
    @State var selected = 1
    
    var body: some View {
        NavigationView{
            VStack{
                Picker("Hello", selection: $selected, content: {
                    Text("All Biases").tag(1)
                    Text("Favorites").tag(2)
                })
                .onChange(of: selected) { tag in //When selected is changed, sort the Favs list and reset search text
                    vm.sortFavs()
                    searchText = ""
                    filterSearchText = ""
                }
                .pickerStyle(SegmentedPickerStyle())
                
                if (selected == 1){
                    List{
                        ForEach(searchText == "" ? BiasStruct.biases : BiasStruct.biases.filter({
                            $0.name.lowercased().contains(searchText.lowercased())
                        }), id: \.id){ entry in
                            HStack{
                                NavigationLink(destination: DetailView( thisBiase: $BiasStruct.biases[entry.id-1]), label: {
                                    Text("\(entry.name)")
                                    Image(systemName: vm.contains(entry) ? "heart.fill" : "heart")
                                        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
                                        .onTapGesture {
                                            vm.toggleFav(item: entry)
                                        }
                                    
                                })
                            }
                        }
                    }
                    .searchable(text: $searchText)
                    .navigationTitle("Biases")
                    .cornerRadius(15)
                    
                }else if (selected == 2){ 
                    List{
                        ForEach(filterSearchText == "" ? vm.filteredItems :vm.filteredItems.filter({
                            $0.name.lowercased().contains(filterSearchText.lowercased())
                        }), id: \.id){ entry in
                            HStack{
                                NavigationLink(destination: DetailView( thisBiase: $BiasStruct.biases[entry.id-1]), label: {
                                    Text("\(entry.name)")
                                    Image(systemName: vm.contains(entry) ? "heart.fill" : "heart")
                                        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
                                        .onTapGesture {
                                            vm.toggleFav(item: entry)
                                        }
                                })
                            }
                        }
                    }
                    .searchable(text: $filterSearchText)
                    .navigationTitle("Favorites")
                    .cornerRadius(15)
                }
            }
        }
        .padding(8)
    }
}

This is the code in the DetailView that toggles the favorite

import SwiftUI

struct DetailView: View {
var body: some View {
Image(systemName: vm.contains(thisBiase) ? "heart.fill" : "heart")
                .frame(alignment: .trailing)
                .padding(10)
                .background(Color(.systemGray4))
                .cornerRadius(8)
                .onTapGesture {
                    vm.toggleFav(item: thisBiase)
                }
}
}

The ViewModel class...

import Foundation
import SwiftUI

@MainActor final class ViewModel: ObservableObject{
    @Published var items = [Biase]()
    @Published var showingFavs = false
    @Published var savedItems: Set<Int> = [1, 7]
    
    // Filter saved items
    var filteredItems: [Biase]  {
        if showingFavs {
            return items.filter { savedItems.contains($0.id) }
        }
        return items
    }
    
    private var BiasStruct: BiasData = BiasData.allBias
    private var db = Database()
    
    init() {
        self.savedItems = db.load()
        self.items = BiasStruct.biases
    }
    
    func sortFavs(){
        withAnimation() {
            showingFavs.toggle()
        }
    }
    
    func contains(_ item: Biase) -> Bool {
        savedItems.contains(item.id)
    }
    
    // Toggle saved items
    func toggleFav(item: Biase) {
        if contains(item) {
            savedItems.remove(item.id)
        } else {
            savedItems.insert(item.id)
        }
        db.save(items: savedItems)
    }
}

I really appreciate your help!

My initial thought was to have a bool var that, when true, would not actually change the favorite value until after the user left its detail view. Even if I could get that to work it's not ideal because if the user leaves the app in its detail view the favorite is not saved.

Upvotes: 3

Views: 178

Answers (1)

malhal
malhal

Reputation: 30746

Yep that was a major problem with NavigationView. Its replacement: NavigationStack and .navigationDestination are designed to resolve it.

Upvotes: 1

Related Questions