I'm about to finish my App, similar to Tinder, but when I can't figure out how to match users based on their food likings, via their Sets of Food.
Is .filter() the way to go?
Video showing user selection of Food and CardView
CardStack will be the compilation of all CardView that will be presented to the user matched in its likings.
struct CardStack: View {
var people: [Person]
#State private var fullscreenMode: Bool = false
var body: some View {
ZStack {
ForEach(people) { person in
CardView(person: person, fullscreenMode: $fullscreenMode)
}
}
}
}
Meanwhile Person is the parameter taken on CardView, that the user will then match to.
struct Person: Hashable, Identifiable {
var id = UUID().uuidString
var foodLikings: Set<Food>
init(id: UUID) {
self.foodLikings = Set(arrayLiteral: Food(id: 3, name: "Empanadas", foodImage: ["empanadas"]), Food(id: 1, name: "Asado", foodImage: ["asado"]))
}
}
Here's what the Set<Food> consists of:
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
}
}
All the available options for Food selection:
class FoodDataService {
static let comidas: [Comida] = [
Comida(
id: 0,
name: "Asado",
foodImage: ["asado"]
),
Food(
id: 1,
name: "Pizzas",
foodImage: ["pizzas"]
),
Food(
id: 2,
name: "Milanesas",
foodImage: ["milanesas"]
),
Food(
id: 3,
name: "Empanadas",
foodImage: ["empanadas"]
)
}
And finally what the User's struct is made of:
var person: Person
var user: User
struct User {
var foodLikings: Set<Food>
}
Found a much easier way to match users by simply using .contains().
if favoriteList.contains(personFavoriteList) {
// Show matched people
} else {
// Show other
}
Where personFavoriteList and favoriteList are strings returned as their likings mapped and sorted.
Related
import Foundation
import SwiftUI
struct Item: Identifiable, Codable{
var id = UUID()
var image: String
var name: String
var price: Int
var isFavorite: Bool
}
class Model: ObservableObject{
#Published var group = [Item]() {
didSet {
if let encoded = try? JSONEncoder().encode(group){
UserDefaults.standard.set(encoded, forKey: "peopleKey")
}
}
}
init(){
if let savedItems = UserDefaults.standard.data(forKey: "peopleKey"),
let decodedItems = try? JSONDecoder().decode([Item].self, from: savedItems) {
group = decodedItems
} else {
group = itemData
}
}
var itemData: [Item] = [
Item(image: "imageGIFT", name: "Flower",price: 5 , isFavorite: false),
Item(image: "imageGIFT", name: "Coffe Cup",price: 9 , isFavorite: false),
Item(image: "imageGIFT", name: "Teddy Bear",price: 2 , isFavorite: false),
Item(image: "imageGIFT", name: "Parfume",price: 8 , isFavorite: false)
]
}
I am trying to change variables on this struct and I define as var but after encode and decode they has been let. I changed let part to var then Xcode gived an error.
here is my test code that shows doing model.people[0].myPerson.toggle() in the Button
does work. I have made some minor mods and added some comments to the code.
I suggest again, read the very basics of Swift,
in particular the array section at: https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html.
Without understanding these very basic concepts you will keep struggling to code your App.
Note, there is probably no need for the myPeople array in your Model, but if that's what you want to have.
struct Person: Identifiable{
let id = UUID()
var name: String
var age: Int
var job: String
var myPerson: Bool
}
class Model: ObservableObject {
#Published var people: [Person] = []
#Published var myPeople: [Person] = []
init(){
addPeople()
}
// completely useless
func addPeople(){
people = peopleData
myPeople = peopleData.filter { $0.myPerson }
}
// here inside the class and using `Person` not `person`
var peopleData = [
Person(name: "Bob", age: 22, job: "Student", myPerson: false),
Person(name: "John", age: 26, job: "Chef", myPerson: false)
]
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
VStack {
VStack {
// this `myPeople` array is empty at first, nothing is displayed
ForEach(model.myPeople) { person in
VStack(alignment: .leading){
Text("Name: \(person.name)").foregroundColor(.blue)
Text("Age: \(person.age)").foregroundColor(.blue)
Text("Job: \(person.job)").foregroundColor(.blue)
Text("myPerson: " + String(person.myPerson)).foregroundColor(.blue)
}.padding()
}
}
VStack {
// this `people` array has two items in it
ForEach(model.people) { person in
VStack(alignment: .leading){
Text("Name: \(person.name)").foregroundColor(.red)
Text("Age: \(person.age)").foregroundColor(.red)
Text("Job: \(person.job)").foregroundColor(.red)
Text("myPerson: " + String(person.myPerson)).foregroundColor(.red)
}.padding()
}
}
Button("Click") {
print("\n--> before name: \(model.people[0].name) ")
print("--> before myPerson: \(model.people[0].myPerson) ")
model.people[0].name = "Franz"
model.people[0].myPerson.toggle()
print("\n--> after name: \(model.people[0].name) ")
print("--> after myPerson: \(model.people[0].myPerson) ")
// update the myPeople array (the blue items)
model.myPeople = model.people.filter { $0.myPerson }
}
}
}
}
Alternatively, you could use this code using only one array of people: [Person]:
class Model: ObservableObject {
#Published var people: [Person] = []
init(){
addPeople()
}
func addPeople() {
people = peopleData
}
// here inside the class and using `Person` not `person`
var peopleData = [
Person(name: "Bob", age: 22, job: "Student", myPerson: false),
Person(name: "John", age: 26, job: "Chef", myPerson: false)
]
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
VStack {
VStack {
// here filter on myPerson=true
ForEach(model.people.filter { $0.myPerson }) { person in
VStack(alignment: .leading){
Text("Name: \(person.name)").foregroundColor(.blue)
Text("Age: \(person.age)").foregroundColor(.blue)
Text("Job: \(person.job)").foregroundColor(.blue)
Text("myPerson: " + String(person.myPerson)).foregroundColor(.blue)
}.padding()
}
}
VStack {
// here filter on myPerson=false
ForEach(model.people.filter { !$0.myPerson }) { person in
VStack(alignment: .leading){
Text("Name: \(person.name)").foregroundColor(.red)
Text("Age: \(person.age)").foregroundColor(.red)
Text("Job: \(person.job)").foregroundColor(.red)
Text("myPerson: " + String(person.myPerson)).foregroundColor(.red)
}.padding()
}
}
Button("Click") {
model.people[0].name = "Franz"
model.people[0].myPerson.toggle()
}
}
}
}
I can't get multi row select to work when using a section list in SwiftUI. And when I select a row from the ordinary list view it renders the section list again.
I have tried changing the id for the list but to no avail, and I have tried to implement my own 'onTapGesture' but again the section list renders every time a tap gesture is recognized and scrolls to the top.
Can someone explain why this happens please?
Code to repro:
import SwiftUI
struct ExerciseList: Identifiable{
let id = UUID()
var categories: [ExerciseCategory] = []
}
struct ExerciseCategory: Identifiable, Hashable{
let id = UUID()
var name: String
var exercises: [Exercise] = []
}
struct Exercise: Codable, Identifiable {
let id: String
let name: String
let category: String
let instructions: String
}
extension Exercise: Hashable {
static func == (lhs: Exercise, rhs: Exercise) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
struct SelectableList: View {
#State var items = [
Exercise(id: "1", name: "Ab Wheel", category: "Core", instructions: ""),
Exercise(id: "2", name: "Aerobics", category: "Core", instructions: ""),
Exercise(id: "3", name: "Arnold Press", category: "Shoulders", instructions: ""),
Exercise(id: "4", name: "Around the World", category: "Full Body", instructions: ""),
Exercise(id: "5", name: "Back Extension", category: "Back", instructions: ""),
Exercise(id: "6", name: "Ball Slams", category: "Core", instructions: ""),
Exercise(id: "7", name: "Crunch", category: "Core", instructions: ""),
]
#State var selectedItem = Set<Exercise>()
func categoriseExercises(exercises: [Exercise]) -> [ExerciseCategory] {
var exercisesList = ExerciseList()
var categorisedExercises = [String:[Exercise]]()
for exercise in exercises{
let exerciseCategory = exercise.category
if categorisedExercises.keys.contains(exerciseCategory){
categorisedExercises[exerciseCategory]?.append(exercise)
}else{
categorisedExercises[exerciseCategory] = [Exercise]()
categorisedExercises[exerciseCategory]?.append(exercise)
}
}
for category in categorisedExercises{
let exerciseCategory = ExerciseCategory(name: category.key, exercises: category.value)
exercisesList.categories.append(exerciseCategory)
}
return exercisesList.categories
}
var body: some View {
NavigationView{
VStack {
List(categoriseExercises(exercises: items)) { section in
Section(header: Text(section.name)){
ForEach(section.exercises){ exercise in
Text(exercise.name)
}
}
}
List(items, id: \.self, selection: $selectedItem){exercise in
Text(exercise.name)
}
}
.navigationTitle("Selectable List")
.toolbar{
EditButton()
}
}
}
}
struct ContentView_Previews1: PreviewProvider {
static var previews: some View {
SelectableList()
}
}
Screenshots of one row selected, then section list renders again after second row selected:
I'm trying to create a List of questions.
I was planning to create a 'section' per question and have each row change upon the type
Now I have a lot of different types of questions.
Let's say for example:
Ask some text input
Select from a picker
Multiple select (so simply show all options)
I have this kind of setup working in 'regular' iOS
However, when trying to implement such a thing in SwiftUI, the preview keeps freeing, I can't seem to get the build working either. I don't really get any feedback from xcode.
Example code:
import SwiftUI
struct Question: Hashable, Codable, Identifiable {
var id: Int
var label: String
var type: Int
var options: [Option]?
var date: Date? = nil
}
struct Option : Hashable, Codable, Identifiable {
var id: Int
var value: String
}
struct MyList: View {
var questions: [Question] = [
Question(id: 1, label: "My first question", type: 0),
Question(id: 2, label: "My other question", type: 1, options: [Option(id: 15, value: "Yes"), Option(id: 22, value: "No")]),
Question(id: 3, label: "My last question", type: 2, options: [Option(id: 4, value: "Red"), Option(id: 5, value: "Green"), Option(id: 6, value: "Blue")])
]
var body: some View {
List {
ForEach(questions) { question in
Section(header: Text(question.label)) {
if(question.type == 0)
{
Text("type 0")
//Show text entry row
}
else if(question.type == 1)
{
Text("type 1")
//Show a picker containing all options
}
else
{
Text("type 2")
//Show rows for multiple select
//
// IF YOU UNCOMMENT THIS, IT STARTS FREEZING
//
// if let options = question.options {
// ForEach(options) { option in
// Text(option.value)
// }
// }
}
}
}
}
}
}
struct MyList_Previews: PreviewProvider {
static var previews: some View {
MyList()
}
}
Is such a thing possible in SwiftUI ?
What am I missing ?
Why is it freezing ?
Your code is confusing the Type checker. If you let it build long enough Xcode will give you an error stating that the type checker could not be run in a reasonable amount of time. I would report this as a bug to Apple, perhaps they can improve the type checker. In the mean time you can get your code to work by simplifying the expression that the ViewBuiler is trying to work through. I did it like this:
import SwiftUI
struct Question: Hashable, Codable, Identifiable {
var id: Int
var label: String
var type: Int
var options: [Option]?
var date: Date? = nil
}
struct Option : Hashable, Codable, Identifiable {
var id: Int
var value: String
}
struct Type0View : View {
let question : Question
var body : some View {
Text("type 0")
}
}
struct Type1View : View {
let question : Question
var body : some View {
Text("type 1")
}
}
struct Type2View : View {
let question : Question
var body : some View {
Text("type 1")
if let options = question.options {
ForEach(options) { option in
Text(option.value)
}
}
}
}
struct ContentView: View {
var questions: [Question] = [
Question(id: 1, label: "My first question", type: 0),
Question(id: 2, label: "My other question", type: 1, options: [Option(id: 15, value: "Yes"), Option(id: 22, value: "No")]),
Question(id: 3, label: "My last question", type: 2, options: [Option(id: 4, value: "Red"), Option(id: 5, value: "Green"), Option(id: 6, value: "Blue")])
]
var body: some View {
List {
ForEach(questions) { question in
Section(header: Text(question.label)) {
if(question.type == 0)
{
Type0View(question: question)
}
else if(question.type == 1)
{
Type1View(question: question)
}
else
{
Type2View(question: question)
}
}
}
}
}
}
struct MyList_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In SwiftUI I have a list of menu items that each hold a name, price etc. There are a bunch of categories and under each are a list of items.
struct ItemList: Identifiable, Codable {
var id: Int
var name: String
var picture: String
var list: [Item]
#State var newItemName: String
}
I was looking for a way to create a TextField inside each category that would add to its array of items.
Making the TextFields through a ForEach loop was simple enough, but I got stuck trying to add a new Item using the entered text to the right category.
ForEach(menu.indices) { i in
Section(header: Text(menu[i].name)) {
ForEach(menu[i].list) { item in
Text(item.name)
}
TextField("New Type:", text: /*some kind of bindable here?*/) {
menu[i].list.append(Item(name: /*the text entered above*/))
}
}
}
I considered using #Published and Observable Object like this other question, but I need the ItemList to be a Codable struct so I couldn't figure out how to fit the answers there to my case.
TextField("New Type:", text: menu[i].$newItemName)
Anyway any ideas would be appreciated, thanks!
You just have to focus your View.
import SwiftUI
struct ExpandingMenuView: View {
#State var menu: [ItemList] = [
ItemList(name: "Milk Tea", picture: "", list: [ItemModel(name: "Classic Milk Tea"), ItemModel(name: "Taro milk tea")]),
ItemList(name: "Tea", picture: "", list: [ItemModel(name: "Black Tea"), ItemModel(name: "Green tea")]),
ItemList(name: "Coffee", picture: "", list: [])
]
var body: some View {
List{
//This particular setup is for iOS15+
ForEach($menu) { $itemList in
ItemListView(itemList: $itemList)
}
}
}
}
struct ItemListView: View {
#Binding var itemList: ItemList
#State var newItemName: String = ""
var body: some View {
Section(header: Text(itemList.name)) {
ForEach(itemList.list) { item in
Text(item.name)
}
TextField("New Type:", text: $newItemName, onCommit: {
//When the user commits add to array and clear the new item variable
itemList.list.append(ItemModel(name: newItemName))
newItemName = ""
})
}
}
}
struct ItemList: Identifiable, Codable {
var id: UUID = UUID()
var name: String
var picture: String
var list: [ItemModel]
//#State is ONLY for SwiftUI Views
//#State var newItemName: String
}
struct ItemModel: Identifiable, Codable {
var id: UUID = UUID()
var name: String
}
struct ExpandingMenuView_Previews: PreviewProvider {
static var previews: some View {
ExpandingMenuView()
}
}
If you aren't using Xcode 13 and iOS 15+ there are many solutions in SO for Binding with array elements. Below is just one of them
ForEach(menu) { itemList in
let proxy = Binding(get: {itemList}, set: { new in
let idx = menu.firstIndex(where: {
$0.id == itemList.id
})!
menu[idx] = new
})
ItemListView(itemList: proxy)
}
Also note that using indices is considered unsafe. You can watch Demystifying SwiftUI from WWDC2021 for more details.
You can have an ObservableObject to be your data model, storing categories which then store the items.
You can then bind to these items, using Swift 5.5 syntax. This means we can write List($menu.categories) { $category in /* ... */ }. Then, when we write $category.newItem, we have a Binding<String> to the newItem property in Category.
Example:
struct ContentView: View {
#StateObject private var menu = Menu(categories: [
Category(name: "Milk Tea", items: [
Item(name: "Classic Milk Tea"),
Item(name: "Taro Milk Tea")
]),
Category(name: "Tea", items: [
Item(name: "Black Tea"),
Item(name: "Green Tea")
]),
Category(name: "Coffee", items: [
Item(name: "Black Coffee")
])
])
var body: some View {
List($menu.categories) { $category in
Section(header: Text(category.name)) {
ForEach(category.items) { item in
Text(item.name)
}
TextField("New item", text: $category.newItem, onCommit: {
guard !category.newItem.isEmpty else { return }
category.items.append(Item(name: category.newItem))
category.newItem = ""
})
}
}
}
}
class Menu: ObservableObject {
#Published var categories: [Category]
init(categories: [Category]) {
self.categories = categories
}
}
struct Category: Identifiable {
let id = UUID()
let name: String
var items: [Item]
var newItem = ""
}
struct Item: Identifiable {
let id = UUID()
let name: String
}
Result:
I'm relatively new to SwiftUI and time to time getting errors and solving them by searching over the internet but this time I could not find any solution to my problem and decided to ask for some help over here, stack overflow. I hope the code below helps you to find my issue.
Both my struct are Identifiable and I actually used ShoppingList struct in the same view to make a List of it with the same technique and it works without an error. But when I try to use ForEach for a variable of ShoppingList struct (which is also a struct and conforms to Identifiable protocol) I get this error "Type of expression is ambiguous without more context"
This is the view that I get my error:
struct ListDetailView: View {
#EnvironmentObject var session: SessionStore
var item: ShoppingList
#State private var isAddNewViewActive: Bool = false
var body: some View {
List {
Section(header: Text("Products")) {
ForEach(self.item.products, id: \.id) { product in <<<--- ERROR LINE
Text(product.name)
}
}
Section(header: Text("")) {
Button(action: { self.isAddNewViewActive.toggle() } ) {
Text("Click to add new product")
}
}
}
.listStyle(GroupedListStyle())
.navigationBarTitle(self.item.name)
.sheet(isPresented: $isAddNewViewActive) {
AddNewItemView(session: self.session, item: self.item, isViewActive: self.$isAddNewViewActive)
}
}
}
These are the structs that are in the code
struct ShoppingList: Identifiable, Equatable {
var id: UUID
var name: String
var coverPhoto: String
var products: [Product]
init(id: UUID = UUID(), name: String, coverPhoto: String = "cart", products: [Product] = [Product]()) {
self.id = id
self.name = name
self.coverPhoto = coverPhoto
self.products = products
}
mutating func addProduct(product: Product) {
products.append(product)
print(products)
}
}
struct Product: Identifiable, Equatable {
var id: UUID
var name: String
var brand: String
var imageURL: String
var links: [Int: String]
var description: String
init(id: UUID = UUID(), name: String, brand: String = "", imageURL: String = "", links: [Int: String] = [:], description: String = "") {
self.id = id
self.name = name
self.brand = brand
self.imageURL = imageURL
self.description = description
self.links = links
}
}
Thanks in advance to all StackOverflow Community.
i properly conform to the Equatable protocol
struct ShoppingList: Identifiable, Equatable {
static func == (lhs: ShoppingList, rhs: ShoppingList) -> Bool {
return lhs.id == rhs.id && rhs.id == lhs.id
}
var id: UUID()
...
init(name: String, brand: String = "", imageURL: String = "", links: [Int: String] = [:], description: String = "") {
...
}
}
no need to init UUID, UUID() will self generate
Apparently, there was an error in a completely unrelated part of the code snippet I posted here (sheet View that pops when I click the button on View that has error) and that was causing the error :/
The code I posted here works just fine.