UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Logic checks linked to all instances, instead of checking independently

Forums > SwiftUI

@00jim  

Attempting to tie each field together with logic to ensure that there is order to the form before being submitted, the theme that I used tries to imply this by remaining white when completed and grey when not.

However, I am having difficulty with instances of IngredientCellView2 as when there are multiple ingredients added they are all linked, thus is one of them is invalid then they all turn grey:

// COLOURS NEEDED FOR ACTIVE AND INACTIVE FIELDS
struct ColourPalette {
    static let standardBackground   = Color(red: 237/255,   green: 241/255, blue: 250/255)
    static let activeText           = Color(red: 43/255,    green: 53/255,  blue: 105/255)
    static let activeBodyTop        = Color(red: 251/255,   green: 240/255, blue: 238/255)
    static let activeBodyBase       = Color(red: 249/255,   green: 228/255, blue: 223/255)
    static let inactiveText         = Color.gray.opacity(0.7)
    static let inactiveTextBody     = Color.gray.opacity(0.2)
    static let activeTextBody       = Color.white
}

// CUSTOM FIELD ALLOWS ME TO CUT DOWN ON MODIFIER DECLARATIONS
struct CustomTextField: View {
    var label: String
    var text: Binding<String>

    var body: some View {
        VStack(alignment: .leading) {
            TextField(label, text: text)
                .padding()
                .background(!text.wrappedValue.isEmpty ? ColourPalette.activeTextBody : ColourPalette.inactiveTextBody)
                .foregroundColor(!text.wrappedValue.isEmpty ? ColourPalette.activeText : ColourPalette.inactiveText)
                .cornerRadius(15)
        }
    }
}

enum Measure {
    case day
}

// I HOPED TO CUSTOMISE THE BUTTON COLOURS HERE, BUT DIDN'T GET AROUND TO IT
struct CustomStepper: View {
    var label: String
    var measurement: Measure
    var min: Int
    var max: Int
    @Binding var value: Int

    var body: some View {
        HStack {
            Stepper(value: $value, in: min...max) {
                Text("\(label) \(value) \(measurementRequest())")
            }
        }
        .padding()
        .background($value.wrappedValue > 0 ? ColourPalette.activeTextBody : ColourPalette.inactiveTextBody)
        .foregroundColor($value.wrappedValue > 0 ? ColourPalette.activeText : ColourPalette.inactiveText)
        .cornerRadius(15)
    }
    private func measurementRequest() -> String {
        switch measurement {
        case .day: return value < 2 ? "day" : "days"
        }
    }
}

// I CAN CUT DOWN ON MODIFIER DECLARATIONS WITH A CUSTOM BUTTON
struct CustomButton: View {
    var title: String
    var action: () -> Void
    var isActive: Bool

    var body: some View {
        Button(action: action) {
            Text(title)
                .padding()
                .foregroundColor(isActive ? ColourPalette.activeText : ColourPalette.inactiveText)
                .frame(maxWidth: .infinity)
        }
        .background(gradient)
        .cornerRadius(15)
        .disabled(!isActive)
    }

    private var gradient: some View {
        LinearGradient(gradient     : Gradient(colors: isActive ? [ColourPalette.activeBodyTop, ColourPalette.activeBodyBase] : [ColourPalette.inactiveTextBody]),
                       startPoint   : .top,
                       endPoint     : .bottom)
    }
}

// THIS IS A SHEET (POP-UP) WHICH WOULD ALLOW ME TO CREATE AN ENTRY (EXPERIMENT)
struct TestView: View {
    @State private var experiment: String = ""
    @State private var ingredients: [Ingredient2] = []
    @State private var maturation: Int = 0
    @State private var destination: String = ""
    @FocusState private var isTextFieldFocused: Bool

    var isAddIngredientButtonEnabled: Bool {
        !experiment.isEmpty && ingredients.allSatisfy { !$0.name.isEmpty && $0.amount > 0 }
    }

    var isSubmitButtonEnabled: Bool {
        !experiment.isEmpty && maturation > 0 && !destination.isEmpty && ingredients.allSatisfy { !$0.name.isEmpty && $0.amount > 0 }
    }

    var body: some View {
            VStack {
                ScrollView {
                    LazyVStack {
                        ZStack {
                            CustomTextField(label: "Experiment Name", text: $experiment)
                            .padding([.leading, .trailing, .bottom], 5)
                        }

                        ForEach(ingredients.indices, id: \.self) { index in
                            IngredientCellView2(ingredient: $ingredients[index], ingredients: $ingredients)
                                .padding(.bottom, 5)
                        }
                        // I NEED INGREDIENTS TO BE TREATED SEPARATELY, SO 1 CARROT, OR 2 TOMATOES WOULD BE THEIR OWN THING
                        CustomButton(title: "Add Ingredient", action: {
                            ingredients.append(Ingredient2(name: "", amount: 0))
                        }, isActive: isAddIngredientButtonEnabled)
                            .padding([.leading, .trailing, .bottom], 5)
                    }
                    .padding()

                CustomStepper(label: "Lifecycle: ", measurement: .day, min: 0, max: 366, value: $maturation)
                    .padding([.leading, .trailing, .bottom], 20)

                CustomTextField(label: "Option: ", text: $destination)
                    .padding([.leading, .trailing, .bottom], 20)
                }

                CustomButton(title: "Submit", action: {}, isActive: isSubmitButtonEnabled)
                    .padding([.leading, .trailing, .bottom], 20)
                    .shadow(color: .gray.opacity(0.2), radius: 10, x: 0, y: 2)
            }
            .background(ColourPalette.standardBackground)
            .edgesIgnoringSafeArea(.all)
    }
}

// INGREDIENTS SHOULD BE REMOVABLE AND THUS KEPT INSIDE AN ARRAY
// THEY SHOULD ALSO BE CHECKED BY LOGIC THAT ALL THEIR FIELDS ARE FILLED FOR THE BACKGROUND TO BE WHITE
struct IngredientCellView2: View {
    @Binding var ingredient: Ingredient2
    @Binding var ingredients: [Ingredient2]
    @FocusState private var isCellFocused: Bool
    @FocusState private var focusedIngredientId: UUID?

    var isComplete: Bool {
        ingredients.allSatisfy { !$0.name.isEmpty && $0.amount > 0 }
    }

    var body: some View {
        VStack {
            TextField("Ingredient Name", text: $ingredient.name)
                .padding(15)
                .foregroundColor(isCellFocused || !$ingredient.name.wrappedValue.isEmpty ? ColourPalette.activeText : ColourPalette.inactiveText)

            Stepper(value: $ingredient.amount, in: 0...99) {
                let quantity: Int = Int(ingredient.amount)
                Text("Amount: \(quantity)")
            }
            .padding(.trailing, 15)
            .foregroundColor(isCellFocused ? ColourPalette.activeText : ColourPalette.inactiveText)

            Button(action: {
                if let index = ingredients.firstIndex(where: { $0.id == ingredient.id }) {
                    ingredients.remove(at: index)
                }
            }) {
                Text("Remove")
                    .foregroundColor(.pink)
                    .padding(5)
            }
        }
        .background(isComplete ? ColourPalette.activeTextBody : ColourPalette.inactiveTextBody)
        .cornerRadius(15)
        .padding(.leading, 5)
        .padding(.trailing, 5)
    }
}

// THIS IS A SINGLE INGREDIENT
struct Ingredient2: Identifiable, Equatable {
    var id = UUID()
    var name: String
    var amount: Int

    static func == (lhs: Ingredient2, rhs: Ingredient2) -> Bool {
        return lhs.id       == rhs.id
            && lhs.name     == rhs.name
            && lhs.amount   == rhs.amount
    }
}

The user should be able to add and modify ingredients as needed, but they background colour only turn white when both the name and amounts are not empty. This should be calculated on an ingredient by ingredient basis, not all or nothing and by adding in a UUID I should be able to get that to happen, but I can't figure it out.

   

If I'm understanding your requirement correctly, the logic error is in the isComplete computed variable. This is used to set the background colour of the cell, but you're checking all of the ingredients to set it. So, if any ingredient is not valid, the background will always be grey.

You should be checking the current ingredient to set the background for that specific ingredient. This will be calculated on a per-row basic so completed rows will be white and incomplete will be grey.

    var isComplete: Bool {
        !ingredient.name.isEmpty && ingredient.amount > 0
//        ingredients.allSatisfy { !$0.name.isEmpty && $0.amount > 0 }
    }

That should sort you out.

Steve

   

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.