Reputation: 81
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
Reputation: 30746
Yep that was a major problem with NavigationView
. Its replacement: NavigationStack
and .navigationDestination
are designed to resolve it.
Upvotes: 1