I have created a message toast type alerts following Fancy Toast Messages
Then I created a view that loops through a json file of ingredients
[
{
"id" : 1,
"name": "Spicy Watermelon Mint Agua Fresca",
"headline": "A refreshing blend of fruity flavor with a blast of spice.",
"image": "Drink1",
"equipment": ["Blender", "Strainer"],
"servingsize": 4,
"drinkware": "Pitcher",
"preptime": 10,
"difficulty": 1.5,
"rating": 2,
"category": ["Agua Fresca"],
"ingredients": [
"2 cups cold water",
"2 cups watermelon (rind removed), seeded and chopped",
"2 tbsp granulated sugar",
"1 tbsp lime juice",
".25 cup mint leaves",
".5 jalapeño, roughly chopped",
"Ice cubes or crushed ice (optional)"
],
"instructions": [
"Add the ingredients into a blender.",
"Blend ingredients until mixture is smooth.",
"Pour mixture through a fine-mesh sieve into a large pitcher, forcing through most of the pulp.",
"Let chill before serving.",
"Serve and enjoy!"
],
"shoppinglist": [
"Watermelon",
"Granulated Sugar",
"Lime Juice",
"Mint Leaves",
"Jalapeño"
]
}
]
Data model
struct Drinks: Codable, Identifiable, Comparable {
static func < (lhs: Drinks, rhs: Drinks) -> Bool {
lhs.name < rhs.name
}
var id: Int
var name: String
var headline: String
var image: String
var equipment: [String]
var servingsize: Int
var drinkware: String
var preptime: Int
var difficulty: Double
var rating: Int
var category: [String]
var ingredients: [String]
var instructions: [String]
var shoppinglist: [String]
}
All is displaying and working correctly.
The issue resides in the view that showing this. I am adding a button that adds the item that is clicked on to a realm database. That aspect is working on a one to one relationship.
What is displaying or exhibiting undesired behavior is when you click on the plus sign its triggering the message alert for every button that is in the stack.
I am not sure how to separate them out to a 1x1 event with the button so after each click it only fires the event once instead of one time for each button that was added to the view for each item in the ingredients list.
Here is the view
import SwiftUI
struct DrinkDetailViewIngredients: View {
let drinks: Drinks
#State private var toast: MessageToast? = nil
var body: some View {
Spacer()
Text("INGREDIENTS")
.fontWeight(.bold)
.modifier(TitleModifier())
ForEach(drinks.ingredients, id: \.self) { item in
VStack(alignment: .leading, spacing: 2) {
HStack {
Text(item)
.lineLimit(nil)
.multilineTextAlignment(.leading)
.font(.system(.body, design: .serif))
.frame(minHeight: 50)
.padding(.leading)
.foregroundColor(.yellow)
Spacer()
Button {
toast = MessageToast(
type: .success,
title: "Item Added",
message: "Item Added to shopping list."
)
} label: {
Image(systemName: "plus")
.foregroundColor(.yellow)
.imageScale(.large)
}
.toastView(toast: $toast)
.buttonStyle(.borderless)
}
}
}
}
}
struct DrinkDetailViewIngredients_Previews: PreviewProvider {
static var previews: some View {
DrinkDetailViewIngredients(drinks: drinks[0])
}
}
This is what happens when you click the plus sign and the message alert is triggered.
Message Alert Duplicates
I figured out the solution to the issue. I embedded the ForEach Loop in a VStack and then moved the message alert to that VStack versus having the message alert on the button itself. Here is the final code.
import SwiftUI
struct DrinkDetailViewIngredients: View {
//MARK: - PROPERTIES
let drinks: Drinks
#State private var toast: MessageToast? = nil
//MARK: - FUNCTIONS
//MARK: - BODY
var body: some View {
// Ingredients
Spacer()
Text("INGREDIENTS")
.fontWeight(.bold)
.modifier(TitleModifier())
VStack {
ForEach(drinks.ingredients, id: \.self) { item in
VStack(alignment: .leading, spacing: 2) {
HStack {
Text(item)
.lineLimit(nil)
.multilineTextAlignment(.leading)
.font(.system(.body, design: .serif))
.frame(minHeight: 50)
.padding(.leading)
.foregroundColor(Color(.yellow)
Spacer()
VStack {
Button {
toast = MessageToast(type: .success, title: "Item Added", message: "Item Added to shopping list.")
} label: {
Image(systemName: "plus")
.foregroundColor(Color(.yellow))
.imageScale(.large)
} //:END OF BUTTON
.buttonStyle(.borderless)
} //:END OF VSTACK
} //:END OF HSTACK
} //: END OF VSTACK INGREDIENTS
}//: END OF LOOP INGREIDENTS
} //: END OF OUTTER VSTACK
.toastView(toast: $toast)
} //: END OF BODY
} //: END OF STRUCT
//MARK: - PREVIEW
struct DrinkDetailViewIngredients_Previews: PreviewProvider {
static var previews: some View {
DrinkDetailViewIngredients(drinks: drinks[0])
}
}
Related
I have 2 sections (ingredients selection and ingredient added). In the ingredient selection, i get all items from a json file. The goal is whenever i chose an ingredient from the ingredient selection, the item should be added to the ingredient added section. However the actual result is that I am able to add all ingredients in AddedIngredients. However my #ObservedObject var ingredientAdded :AddedIngredients indicates no items, so nothing in the section.
What am I missing in my logic?
Is it because the #Published var ingredients = [Ingredient]() is from a struct which creates a new struct every time ? My understanding is that since AddedIngredients is a class, it should reference the same date unless I override it.
This my model:
import Foundation
struct Ingredient: Codable, Identifiable {
let id: String
let price: String
let ajoute: Bool
}
class AddedIngredients: ObservableObject {
#Published var ingredients = [Ingredient]()
}
The ingredient section that displays all ingredients:
struct SectionIngredientsSelection: View {
let ingredients: [Ingredient]
#StateObject var ajoute = AddedIngredients()
var body: some View {
Section(header: VStack {
Text("Ajouter vos ingredients")
}) {
ForEach(ingredients){ingredient in
HStack{
HStack{
Image("mais")
.resizable()
.frame(width: 20, height: 20)
Text(ingredient.id)
}
Spacer()
HStack{
Text(ingredient.price )
Button(action: {
ajoute.ingredients.append(ingredient)
print(ajoute.ingredients.count)
}){
Image(systemName: "plus")
.foregroundColor(Color(#colorLiteral(red: 0, green: 0.3257463574, blue: 0, alpha: 1)))
}
}
}
}
.listRowBackground(Color.white)
.listRowSeparator(.hidden)
}
}
}
Whenever I add an ingredient from the previous section, it should appear on this section.
struct SectionIngredientsSelected: View {
#ObservedObject var ingredientAdded :AddedIngredients
var body: some View {
Section(header: VStack {
HStack{
Text("Vos ingredients")
.textCase(nil)
.font(.headline)
.fontWeight(.bold)
.foregroundColor(.black)
Button(action: {print(ingredientAdded.ingredients.count)}, label: {Text("Add")})
}
}) {
ForEach(ingredientAdded.ingredients){ingredient in
HStack{
HStack{
Image("mais")
.resizable()
.frame(width: 20, height: 20)
Text(ingredient.id)
}
Spacer()
HStack{
Text(ingredient.price )
Button(action: {
}){
Image(systemName: "xmark.circle")
.foregroundColor(Color(#colorLiteral(red: 0, green: 0.3257463574, blue: 0, alpha: 1)))
}
}
}
}
.listRowBackground(Color.white)
.listRowSeparator(.hidden)
}
}
}
Main View
struct CreationView: View {
let ingredients: [Ingredient] = Bundle.main.decode("Ingredients.json")
var addedIngre: AddedIngredients
var body: some View {
ScrollView {
VStack{
VStack(alignment: .leading) {
Image("creation")
.resizable()
.scaledToFit()
Text("Slectionnez vos ingredients preferes").fontWeight(.light)
Divider()
.padding(.bottom)
}
}
}
List {
SectionIngredientsSelected(ingredientAdded: addedIngre)
SectionIngredientsSelection(ingredients: ingredients)
}
}
}
You are creating two different instances of AddedIngredients. These do not synchronize their content with each other. The simpelest solution would be to pull the AddedIngredients up into CreationView and pass it down to the sub views.
So change your CreationView to:
struct CreationView: View {
let ingredients: [Ingredient] = Bundle.main.decode("Ingredients.json")
#StateObject private var addedIngre: AddedIngredients = AddedIngredients()
and:
SectionIngredientsSelected(ingredientAdded: addedIngre)
SectionIngredientsSelection(ingredients: ingredients, ajoute: addedIngre)
and SectionIngredientsSelection to:
struct SectionIngredientsSelection: View {
let ingredients: [Ingredient]
#ObservedObject var ajoute: AddedIngredients
Remarks:
It is not perfectly clear for me how CreationView aquires addedIngre. From your code it seems it gets somehow injected. If this is the case and you need it upward the view hierachy, change the #StateObject to #ObservedObject. But make sure it is initialized with #StateObject.
I am building an app that will store multiple Nintendo consoles and their details (kinda like Mactracker but for Nintendo stuff).
I wanna store consoles that the user chooses in a favourites category on the main menu but I can't implement it correctly.
I have the main menu which shows the different categories as well as the favourite category which is duplicated for each category:
Each favourites "button" takes me to the favourites of only a specific category (the category beneath it) and I would like that all of them are in only one tab and not in multiple tabs like they are so far.
The favourites category is a bool defined in the Console List which will be listed below.
Thanks for the help and sorry if its a stupid question, I'm quite new to programming.
Main Menu:
struct MainMenu: View {
// Use categories ordered by alphabetical order
var categories = ConsoleList.categories.sorted(by: {$0.key < $1.key})
var body: some View {
// Loop on categories
NavigationView{
List(categories, id:\.key){category in
// The NavigationLink that takes me to the favorites view
NavigationLink(destination: Favorites(con: category.value)){
Image(systemName: "heart")
Text("Favorites")
.fontWeight(.semibold)
}
NavigationLink(destination: ConsoleMenu(con: category.value), label:{
Image(systemName: "folder")
.foregroundColor(.red)
.scaledToFit()
.frame(height: 30)
.cornerRadius(4)
.padding(.vertical, 4)
VStack{
Text(category.key)
.fontWeight(.semibold)
}
})
}
.navigationTitle("")
}
}
}
Favourites menu:
struct Favorites: View {
var con: [ConsoleDetails]
var body: some View {
List(con){ cons in
if cons.favorites {
NavigationLink(destination: ConsoleDetailView(con: cons), label:{
Image(cons.imgName)
.resizable()
.scaledToFit()
.frame(height: 50)
.cornerRadius(4)
.padding(.vertical, 4)
.navigationTitle("\(cons.category)")
VStack (alignment: .leading){
if cons.category == "Game & Watch" {
Text(cons.consoleName)
.fontWeight(.semibold)
Text(cons.mostSoldGame)
.font(.subheadline)
}else{
Text(cons.consoleName)
.fontWeight(.semibold)
}
}
}).navigationTitle("Favorites")
}
}
}
}
struct favorites_Previews: PreviewProvider {
static var previews: some View {
Favorites(con: ConsoleList.consoles)
}
}
The consoles list:
struct ConsoleDetails: Identifiable{
let id = UUID()
var imgName: String = ""
var consoleName: String = ""
var mostSoldGame: String = ""
var initialPrice: String = ""
var ReleaseDate: String = ""
var Discontinuation: String = ""
var category: String = ""
var estimatedPricedToday: String = ""
var cables: String = ""
var favorites: Bool
}
struct ConsoleList{
//The consoles list has more consoles usually but I'll only put one to save space
static var categories = Dictionary(grouping: consoles, by: {$0.category } )
static var favs = Dictionary(grouping: consoles, by: {$0.favorites} )
static var consoles = [
//Current Consoles
ConsoleDetails(imgName: "NS_OG",
consoleName: "Nintendo Switch",
mostSoldGame: "Mario Kart 8 Deluxe",
initialPrice: "299.99",
ReleaseDate: "Mar 3, 2017",
Discontinuation: "Still Available",
category: "Current Consoles",
estimatedPricedToday: "$200-250 used",
cables: "HDMI, USB Type-C",
favorites: true),
Edit: I have added what was proposed but I get an error in the nav link. See below:
List{
Section {
// The NavigationLink that takes me to the favorites view
NavigationLink {
Favorites() // Right Here: Missing argument for parameter 'con' in call
} label: {
Image(systemName: "heart")
Text("Favorites")
.fontWeight(.semibold)
}
}
Section {
ForEach(categories, id: \.key){ category in
NavigationLink(destination: ConsoleMenu(con: category.value), label:{
Image(systemName: "folder")
.foregroundColor(.red)
.scaledToFit()
.frame(height: 30)
.cornerRadius(4)
.padding(.vertical, 4)
VStack{
Text(category.key)
.fontWeight(.semibold)
}
})
}
}
}
Instead of having the list loop over the categories, you could put a ForEach in the List.
Then, you could have one favorites link, and put it in a different section to differentiate it.
List {
Section {
NavigationLink {
Favorites(con: /* (1) */ ConsoleList.consoles)
} label: { /* ... */ }
}
Section {
ForEach(categories, id: \.key) { category in
// ...
}
}
}
(1) You will also need to pass in a list of all the consoles to show.
I am building an app that will store multiple Nintendo consoles and their details (kinda like Mactracker but for Nintendo stuff).
I wanna store certain consoles in categories on the main menu but I'm not sure how I could do it (I'm pretty new to swiftui so sorry if that's a stupid question).
I already have categories set on MainMenu based on the category I've put in my consoles array. But I can't get the console menu (where all the consoles are) to store the consoles only of the category I tap on.
My consoles array has multiple consoles but I've put just one to save space.
Main Menu:
import SwiftUI
struct MainMenu: View {
var con: [ConsoleDetails] = ConsoleList.consoles
var body: some View {
NavigationView{
List(ConsoleList.categories.sorted(by: {$0.key > $1.key}), id:\.key){con in
NavigationLink(destination: ConsoleMenu(), label:{
Image(systemName: "folder.fill")
.scaledToFit()
.frame(height: 30)
.cornerRadius(4)
.padding(.vertical, 4)
VStack{
Text(con.key)
.fontWeight(.semibold)
}
}).navigationTitle("app.name")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MainMenu()
}
}
ConsoleMenu:
import SwiftUI
struct ConsoleMenu: View {
var con: [ConsoleDetails] = ConsoleList.consoles
var body: some View {
NavigationView{
List(con, id:\.id){ cons in
NavigationLink(destination: ConsoleDetailView(con: cons), label:{
Image(cons.imgName)
.resizable()
.scaledToFit()
.frame(height: 50)
.cornerRadius(4)
.padding(.vertical, 4)
VStack{
Text(cons.consoleName)
.fontWeight(.semibold)
}
}).navigationTitle("\(cons.category)")
}
}
}
}
struct ConsoleSection_Previews: PreviewProvider {
static var previews: some View {
ConsoleMenu()
.preferredColorScheme(.dark)
}
}
Consoles:
import Foundation
struct ConsoleDetails: Identifiable{
let id = UUID()
var imgName: String = ""
var consoleName: String = ""
var description: String = ""
var initialPrice: Double = 0.0
var ReleaseDate: String = ""
var Discontinuation: Int = 0
var category: String = ""
}
struct ConsoleList{
static let categories = Dictionary(grouping: consoles, by: {$0.category } )
static let consoles = [
ConsoleDetails(imgName: "FAMICOM",
consoleName: "Famicom",
description: "It was released in 1983 in Japan and due to it success it gave birth to the NES",
initialPrice: 179,
ReleaseDate: "Release Date: July 15, 1983",
Discontinuation: 2003,
category: "Home Consoles"),
//there's more consoles but I just put one to save space on here
Few modifications:
In main view use only categories and pass only useful consoles to console menu :
struct MainMenu: View {
// Use categories ordered by reversed alphabetical order
var categories = ConsoleList.categories.sorted(by: {$0.key > $1.key})
// var con: [ConsoleDetails] = ConsoleList.consoles
var body: some View {
NavigationView{
// Loop on categories
List(categories, id:\.key){category in
NavigationLink(destination: ConsoleMenu(con: category.value), label:{
Image(systemName: "folder.fill")
.scaledToFit()
.frame(height: 30)
.cornerRadius(4)
.padding(.vertical, 4)
VStack{
Text(category.key)
.fontWeight(.semibold)
}
}).navigationTitle("app.name")
}
}
}
}
In Console menu use the consoles furnished by main menu
struct ConsoleMenu: View {
// Consoles are given by caller
var con: [ConsoleDetails] /* = ConsoleList.consoles */
var body: some View {
NavigationView{
List(con, id:\.id){ cons in
NavigationLink(destination: ConsoleDetailView(con: cons), label:{
Image(cons.imgName)
.resizable()
.scaledToFit()
.frame(height: 50)
.cornerRadius(4)
.padding(.vertical, 4)
VStack{
Text(cons.consoleName)
.fontWeight(.semibold)
}
}).navigationTitle("\(cons.category)")
}
}
}
}
I am trying to append to a struct from another file. I can do so, and it works in one file, but when I try to connect the second, it doesn't work.
I am trying to implement a checkout feature, and right now, I need to at least be able to append items to the cart.
currentOrderLogic.swift
struct Cart: Hashable, Identifiable {
let id = UUID()
var product_name: String
var product_cost: String
}
public struct CurrentOrder: View {
#State var items = [Cart]()
func addObject(product_name: String, product_cost: String) {
items.append(Cart(product_name: product_name, product_cost: product_cost))
}
public var body: some View {
ScrollView(.vertical) {
VStack(alignment: .center) {
Text("Current Order".uppercased()).font(.system(size: 30))
.frame(height:50)
ForEach(items, id: \.self) { item in
HStack{
Text(item.product_name)
Text(item.product_cost)
}
}
Spacer()
Button("Charge $0.00") {
addObject(product_name: "Aooke", product_cost: "6")
}
}
}
}
}
When I press the button, it is added and shown in the list.
Now, I am trying to have items that they can click, and when it is clicked, the item is added to the current order.
homeMenu.swift
struct homeMenuObject: View {
#State var posts: [Post] = []
let date = Date()
var body: some View {
VStack {
HStack {
Text(date, style: .date)
Text(date, style: .time)
}
WrappingHStack(posts, id: \.self){ item in
VStack {
HStack {
Image("Logo").resizable().frame(width: 110, height: 55)
}
HStack {
Text(item.title)
}
HStack {
Text((item.body).uppercased())
.font(.headline)
}
}.frame(width: 110, height: 140).background(Color.white).onTapGesture {
CurrentOrder().addObject(product_name: "Aooke", product_cost: "6")
}
}.onAppear {
Api().getPosts { (posts) in
self.posts = posts
}
}
Spacer()
}
}
}
But when CurrentOrder().addObject(product_name: "Aooke", product_cost: "6") is called, nothing happens. How can I fix this?
Using ObservedObjects, I figured this out MYSELF.
currentOrderLogic.swift
public struct CurrentOrder: View {
#ObservedObject var cartSystem : CartSystem
public var body: some View {
ScrollView(.vertical) {
VStack(alignment: .center) {
Text("Current Order".uppercased()).font(.system(size: 30))
.frame(height:50)
ForEach(cartSystem.items, id: \.self) { item in
HStack{
Text(item.product_name)
Text(item.product_cost)
}
}
Spacer()
Button("Charge $0.00") {
cartSystem.addObject(product_name: "Aooke", product_cost: "6")
}
}
}
}
}
homeMenu.swift
struct homeMenuObject: View {
#State var posts: [Post] = []
#ObservedObject var cartSystem : CartSystem
let date = Date()
var body: some View {
VStack {
HStack {
Text(date, style: .date)
Text(date, style: .time)
}
WrappingHStack(posts, id: \.self){ item in
VStack {
HStack {
Image("Logo").resizable().frame(width: 110, height: 55)
}
HStack {
Text(item.title)
}
HStack {
Text((item.body).uppercased())
.font(.headline)
}
}.frame(width: 110, height: 140).background(Color.white).onTapGesture {
cartSystem.addObject(product_name: "Aooke", product_cost: "6")
}
}.onAppear {
Api().getPosts { (posts) in
self.posts = posts
}
}
Spacer()
}
}
}
Then, in your main ContentView, I put this outside of the ContentView Structure,
class CartSystem: ObservableObject {
#Published var items = [Cart]()
func addObject(product_name: String, product_cost: String) {
self.items.append(Cart(product_name: product_name, product_cost: product_cost))
}
struct Cart: Hashable, Identifiable {
let id = UUID()
var product_name: String
var product_cost: String
}
}
Then I put this inside the ContentView struct
#StateObject var cartSystem : CartSystem = CartSystem()
Then, when I call the CurrentOrder struct and the HomeMenu struct, you pass this cartSystem variable in. This makes all of the CartSystem's the same, so you are not creating new instances of them and makes them connected.
Now i need to make the homeMenu objects pass their respective data into the order
I'm trying to develop a Picker with a field that corresponds to the title. The problem is that I'm not understanding how to use the title field of the Picker view.
This is the code. The problem is that the Picker is taking as title the string "Spain". Instead I want the title "Select country" which is visible until the user select a field.
struct CustomPicker: View {
#State private var selection = "Select country"
let colors = ["Spain", "France"]
var body: some View {
VStack(alignment: .leading, spacing: 4, content: {
HStack(spacing: 15) {
Picker("Select country", selection: $selection) {
ForEach(colors, id: \.self) {
Text($0)
}
}
.pickerStyle(DefaultPickerStyle())
}
.padding(.horizontal, 20)
})
.frame(height: 50)
.background(.white)
.cornerRadius(10)
.padding(.horizontal, 20)
}
}
What you're trying to do doesn't come standard with SwiftUI. You would have to custom make your UI for this (and that might not be hard). Depending on how much you're willing to compromise, you can have what you want with a slight tweak of your code. This is what a picker looks like within a List (as well as your Picker in the List).
To do this I modified your code slightly to include an enum for the countries.
enum Country: String, CaseIterable, Identifiable {
case spain, france, germany, switzerland
var id: Self { self }
}
struct CustomPicker: View {
#State private var selection: Country = .spain
var body: some View {
NavigationView {
List {
Picker("Select Country", selection: $selection) {
ForEach(Country.allCases, id: \.self) {
Text($0.rawValue.capitalized)
.tag($0)
}
}
Picker("Select Country", selection: $selection) {
ForEach(Country.allCases, id: \.self) {
Text($0.rawValue.capitalized)
.tag($0)
}
}
.pickerStyle(.menu)
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Country Picker")
}
}
}