After I choose and favourite a personality, it will get added to my “Favorites” view and exhibits a coronary heart subsequent to the character identify within the record. Nevertheless, I am operating into a difficulty the place when I attempt to un-favorite the character, my FavoritesView provides the character quite than eradicating it. My guess is the best way I am passing my knowledge, however cannot determine it out. I’ve tried alternative ways on passing the information to attempt to repair the issue.
TabView
struct CharactersTabView: View {
@StateObject var favorites = Favorites()
var physique: some View {
TabView {
//MARK: - Character Tab
ContentView()
.tabItem {
Label("Characters", systemImage: "individual.3.fill")
}
//MARK: - Favorites Tab
FavoritesView()
.tabItem {
Label("Favorites", systemImage: "coronary heart.fill")
}
}//TabView
.environmentObject(favorites)
}
}
ContentView
struct ContentView: View {
@EnvironmentObject var mannequin: CharacterViewModel
@StateObject var favorites = Favorites()
var previousButton: some View {
Button("Earlier") {
Job {
await mannequin.previousPage()
}
}
.disabled(!mannequin.hasPreviousPage)
}
var nextButton: some View {
Button("Subsequent") {
Job {
await mannequin.nextPage()
}
}
.disabled(!mannequin.hasNextPage)
}
//MARK: - Physique
var physique: some View {
NavigationView {
Listing(mannequin.filteredCharacters){
character in
NavigationLink {
CharacterDetailsView(character: character)
} label: {
HStack{
CharacterRowView(imageUrlString: character.picture, identify: character.identify, species: character.species)
if favorites.incorporates(character) {
Spacer()
Picture(systemName: "coronary heart.fill")
.accessibilityLabel("This can be a favourite character")
.foregroundColor(.purple)
}
}
}
}
.searchable(textual content: $mannequin.searchText, immediate: "Seek for a personality")
.onChange(of: mannequin.searchText, carry out: { newValue in
Job {
await mannequin.fetchallCharacters()
}
})
.toolbar{
ToolbarItem(placement: .navigationBarLeading) {
previousButton
}
ToolbarItem(placement: .navigationBarTrailing) {
nextButton
}
}
.navigationBarTitle("Characters")
}//Navigationview
.job({
await mannequin.fetchallCharacters()
})
.phoneOnlyNavigationView()
.environmentObject(favorites)
//attaching favorites to the setting on NavigationView. So each view the nagivation view presents will even acquire that Favorites instace to work with
}
}
CharactersDetailView the place the consumer can select to favourite the character
struct CharacterDetailsView: View {
//Must reference for element view by counting on the record to assign the main points.
var character: Character
@EnvironmentObject var favorites: Favorites
@State personal var isFavorited = false
//MARK: - Computed Property
var favoriteText: String {
if isFavorited {
return "Favorited"
} else {
return "Favourite"
}
}
//MARK: - Favourite Button Supplementary view
var favoriteButton: some View {
Button {
isFavorited.toggle()
if favorites.incorporates(character) {
//If favorites incorporates a personality, we try to un-favorite and take away it
favorites.take away(character)
} else {
favorites.add(character)
}
} label: {
ZStack {
if isFavorited == false {
Capsule()
.strokeBorder(Shade.inexperienced, lineWidth: 4)
.body(width: 250, peak: 50)
.background(
Capsule()
.fill(.inexperienced)
.cornerRadius(20)
)
} else {
Capsule()
.strokeBorder(Shade.blue, lineWidth: 4)
.body(width: 250, peak: 50)
.background(
Capsule()
.fill(.blue)
.cornerRadius(20)
)
}
HStack {
Textual content(favoriteText)
.foregroundColor(.white)
Picture(systemName: favorites.incorporates(character) ? "coronary heart.fill" : "coronary heart")
.foregroundColor(favorites.incorporates(character) ? .white : .white)
}
}
}
.padding(.vertical)
.onAppear {
isFavorited = favorites.incorporates(character)
}
}
//MARK: - Physique
var physique: some View {
ScrollView {
VStack {
//MARK: - Picture
AsyncImage(url: URL(string: character.picture)) {
part in
if let picture = part.picture {
picture
.resizable()
.scaledToFit()
} else if part.error != nil {
Textual content("Could not add photograph")
} else {
ProgressView()
}
}
//MARK: - Character Particulars
VStack(alignment: .main) {
Textual content(character.identify)
.font(.largeTitle)
.daring()
.padding(.main)
VStack(spacing: 5) {
SubDetailView(sfImageString: "checkmark.circle", infoText: "Standing", detailText: character.standing)
SubDetailView(sfImageString: "individual.circle", infoText: "Species", detailText: character.species)
SubDetailView(sfImageString: "home.circle", infoText: "Origin", detailText: character.origin.identify)
SubDetailView(sfImageString: "mappin.circle", infoText: "Location", detailText: character.location.identify)
}
.padding(.horizontal)
}
//MARK: - Favourite Button
favoriteButton
}
}
}
}
FavoritesView
struct FavoritesView: View {
@EnvironmentObject var mannequin: CharacterViewModel
@EnvironmentObject var favorites: Favorites
var physique: some View {
Listing(mannequin.filteredCharacters) { character in
if favorites.incorporates(character) {
HStack {
CharacterRowView(imageUrlString: character.picture, identify: character.identify, species: character.species)
}
}
}
}
}
Favorites logic for saving/loading knowledge
class Favorites: ObservableObject {
personal var characters: Set<String>
personal let saveKey = "SaveFavorites"
init(){
//load saved knowledge
if let knowledge = UserDefaults.commonplace.knowledge(forKey: saveKey) {
//Utilizing Set<String> as a result of we do not wish to retailer your complete struct, simply the id
if let decoded = attempt? JSONDecoder().decode(Set<String>.self, from: knowledge) {
characters = decoded
return
}
}
characters = []
}
func incorporates(_ character: Character) -> Bool {
characters.incorporates(String(character.id))
}
func add(_ character: Character) {
objectWillChange.ship()
characters.insert(String(character.id))
save()
}
func take away(_ character: Character) {
objectWillChange.ship()
characters.take away(String(character.id))
save()
}
func save(){
if let encoded = attempt? JSONEncoder().encode(characters) {
UserDefaults.commonplace.set(encoded, forKey: saveKey)
}
}
}