I have list view in SwiftUI, and when I use context menu, I want to put the star icon for item as a Favorite or Unfavorite , when I click the text button, it show the Favorite text, but when I click the another item it show Unfavorite text, I do not know how to solve it, any idea?
favoritemodel:
import SwiftUI
struct FavoriteModel{
var isFavorite: Bool = false
var timeStamp: Date = Date()
var userIds: Set<UUID> = []
mutating func toogleFavorite(userId: UUID){
isFavorite.toggle()
timeStamp = Date()
if isFavorite{
userIds.insert(userId)
}else{
userIds.remove(userId)
}
}
}
model:
struct Restaurant: Identifiable {
var id = UUID()
var name: String
var image: String
var isFavorite: Bool = false
}
BasicImageRow:
struct BasicImageRow: View {
var restaurant: Restaurant
var body: some View {
HStack {
Image(restaurant.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(restaurant.name)
if restaurant.isFavorite {
Spacer()
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
}
}
view:
import SwiftUI
struct ContentView: View {
@State var showAnswer = false
@State var toggleText = false
@State private var selectedRestaurant: Restaurant?
@State private var restaurants = [ Restaurant(name: "Cafe Deadend", image: "cafedeadend"),
Restaurant(name: "Homei", image: "homei"),
Restaurant(name: "Teakha", image: "teakha"),
Restaurant(name: "Cafe Loisl", image: "cafeloisl"),
Restaurant(name: "Petite Oyster", image: "petiteoyster"),
Restaurant(name: "For Kee Restaurant", image: "forkeerestaurant"),
]
var body: some View {
List {
ForEach(restaurants) { restaurant in
BasicImageRow(restaurant: restaurant)
.contextMenu {
Button(action: {
self.showAnswer = true
self.toggleText.toggle()
self.setFavorite(item: restaurant)
}) {
HStack {
Text(toggleText ? "Favorite" : "UnFavorite")
Text(toggleText ? "UnFavorite" : "Favorite")
Image(systemName: "star")
}
}
}
.onTapGesture {
self.selectedRestaurant = restaurant
}
.actionSheet(item: self.$selectedRestaurant) { restaurant in
ActionSheet(title: Text("What do you want to do"), message: nil, buttons: [
.default(Text("Mark as Favorite"), action: {
self.setFavorite(item: restaurant)
}),
.cancel()
])
}
}
}
}
private func setFavorite(item restaurant: Restaurant) {
if let index = self.restaurants.firstIndex(where: { $0.id == restaurant.id }) {
self.restaurants[index].isFavorite.toggle()
}
}
}
CodePudding user response:
If I understand the question correctly, try this approach by removing all toggleText
and using @Ali Momeni advice:
struct ContentView: View {
@State var showAnswer = false
@State private var selectedRestaurant: Restaurant?
@State private var restaurants = [ Restaurant(name: "Cafe Deadend", image: "cafedeadend"),
Restaurant(name: "Homei", image: "homei"),
Restaurant(name: "Teakha", image: "teakha"),
Restaurant(name: "Cafe Loisl", image: "cafeloisl"),
Restaurant(name: "Petite Oyster", image: "petiteoyster"),
Restaurant(name: "For Kee Restaurant", image: "forkeerestaurant")
]
private func RestaurantRow(restaurant: Restaurant) -> some View {
BasicImageRow(restaurant: restaurant)
.contextMenu {
Button(action: {
showAnswer = true
setFavorite(item: restaurant)
}) {
HStack {
Text(restaurant.isFavorite ? "UnFavorite" : "Favorite")
Image(systemName: "star")
}
}
}.id(restaurant.id)
.onTapGesture {
selectedRestaurant = restaurant
}
.actionSheet(item: $selectedRestaurant) { restaurant in
ActionSheet(title: Text("What do you want to do"),
message: nil,
buttons: [
.default(
Text(restaurant.isFavorite ? "Mark as UnFavorite" : "Mark as Favorite"),
action: {
setFavorite(item: restaurant)
}),
.cancel()
])
}
}
var body: some View {
List {
ForEach(restaurants) { restaurant in
if restaurant.isFavorite {
RestaurantRow(restaurant: restaurant)
} else {
RestaurantRow(restaurant: restaurant)
}
}
}
}
private func setFavorite(item restaurant: Restaurant) {
if let index = restaurants.firstIndex(where: { $0.id == restaurant.id }) {
restaurants[index].isFavorite.toggle()
}
}
}
CodePudding user response:
I recommend changing your model to be like how Apple's Fruta sample implements favourites. E.g.
class Model: ObservableObject {
...
@Published var favoriteSmoothieIDs = Set<Smoothie.ID>()
func toggleFavorite(smoothieID: Smoothie.ID) {
if favoriteSmoothieIDs.contains(smoothieID) {
favoriteSmoothieIDs.remove(smoothieID)
} else {
favoriteSmoothieIDs.insert(smoothieID)
}
}
func isFavorite(smoothie: Smoothie) -> Bool {
favoriteSmoothieIDs.contains(smoothie.id)
}
struct SmoothieFavoriteButton: View {
@EnvironmentObject private var model: Model
var isFavorite: Bool {
guard let smoothieID = model.selectedSmoothieID else { return false }
return model.favoriteSmoothieIDs.contains(smoothieID)
}
var body: some View {
Button(action: toggleFavorite) {
if isFavorite {
Label {
Text("Remove from Favorites", comment: "Toolbar button/menu item to remove a smoothie from favorites")
} icon: {
Image(systemName: "heart.fill")
}
} else {
Label {
Text("Add to Favorites", comment: "Toolbar button/menu item to add a smoothie to favorites")
} icon: {
Image(systemName: "heart")
}
}
}
.disabled(model.selectedSmoothieID == nil)
}
func toggleFavorite() {
guard let smoothieID = model.selectedSmoothieID else { return }
model.toggleFavorite(smoothieID: smoothieID)
}
}
struct SmoothieFavoriteButton_Previews: PreviewProvider {
static var previews: some View {
SmoothieFavoriteButton()
.padding()
.previewLayout(.sizeThatFits)
.environmentObject(Model())
}
}
As you can see using structs for model data does require some re-thinking.