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.