My app consists on an image of various foods, in which the user taps the image and adds this food into a Set<Food>.
I want to show all items from this Set inside the class called Favorites, as a: Text("You like: \(favorites.comidas)") but I can't manage to make it work
class Favorites: ObservableObject {
var foods: Set<Food>
}
class Favorites: ObservableObject {
var foods: Set<Food>
init() {
// load the saved data
foods = []
}
func contains(_ food: Food) -> Bool {
foods.contains(food)
}
func add(_ food: Food) {
objectWillChange.send()
foods.insert(food)
save()
}
func delete(_ food: Food) {
objectWillChange.send()
foods.remove(food)
save()
}
}
struct Food: Identifiable, Hashable {
var id: Int
let name: String
let foodImage: [String]
// Equatable
static func == (lhs: Food, rhs: Food) -> Bool {
lhs.id == rhs.id
}
}
@EnvironmentObject var favorites: Favorites
let food: Food
var body: Some View {
Image(food.foodImage[0])
.onTapGesture {
if favorites.contains(food) {
favorites.delete(food)
} else {
favorites.add(food)
}
}
}
CodePudding user response:
You haven't shown your Food structure, but I will assume it has a property, name.
ListFormatter is your friend with a task like this. Its string(from:[]) function takes an array and returns it in a nicely formatted list. You can use map to get an array of name strings from your set.
For the input array ["pizza","tacos","chocolate"] it will give "pizza, tacos and chocolate"
var favoriteList: String {
let formatter = ListFormatter()
let favorites = formatter.string(from:self.favorites.foods.map{$0.name})
return favourites ?? ""
}
Then you can use this function in a Text view:
Text("You like \(self.favoriteList)")
Note that a Set is unordered, so it might be nice to sort the array so that you get a consistent, alphabetical order:
var favoriteList: String {
let formatter = ListFormatter()
let favorites = formatter.string(from:self.favorites.foods.map{$0.name}.sorted())
return favourites ?? ""
}
Thanks to a tip from Leo Dabus in the comments, in Xcode 13 and later you can just use .formatted -
var favoriteList: String {
return self.favorites.foods.map{$0.name}.sorted().formatted() ?? ""
}
CodePudding user response:
To answer your question How to display all items from a Set,
you could try the following approach, works for me:
@main
struct TestApp: App {
@StateObject var favorites = Favorites() // <-- here
let food: Food = Food(id: 1, name: "info", foodImage: ["info"])
var body: some Scene {
WindowGroup {
ContentView(food: food)
.environmentObject(favorites) // <-- here
}
}
}
struct Food: Identifiable, Hashable {
var id: Int
let name: String
let foodImage: [String]
// Equatable
static func == (lhs: Food, rhs: Food) -> Bool {
lhs.id == rhs.id
}
}
struct ContentView: View {
@EnvironmentObject var favorites: Favorites
let food: Food
var body: some View {
List {
ForEach(Array(favorites.foods)) { item in // <-- here
if let img = UIImage(systemName: item.foodImage.first ?? "questionmark") {
VStack {
Text("You like: \(item.name)")
Image(uiImage: img)
.onTapGesture { // here, strange logic, but that's what you want
if favorites.contains(food) {
favorites.delete(food)
} else {
favorites.add(food)
}
}
}
}
}
}
.onAppear {
favorites.foods.insert(Food(id: 2, name: "globe", foodImage: ["globe"]))
favorites.foods.insert(Food(id: 3, name: "folder", foodImage: ["folder"]))
}
}
}
class Favorites: ObservableObject {
@Published var foods: Set<Food> = [] // <-- here
init() { }
func contains(_ food: Food) -> Bool {
foods.contains(food)
}
func add(_ food: Food) {
foods.insert(food)
save()
}
func delete(_ food: Food) {
foods.remove(food)
save()
}
func save() {
// save your data here
}
}
