With lesson 6 of module 7 of the CWC+ iOS Databases course, we see how to capture user input.
Lesson 6
We have a new SwiftUI view for our input form:
import SwiftUI
struct AddRecipeView: View {
@State private var name = ""
@State private var summary = ""
@State private var prepTime = ""
@State private var cookTime = ""
@State private var totalTime = ""
@State private var servings = ""
@State private var highlights = [String]()
@State private var directions = [String]()
var body: some View {
...
VStack {
AddMetaData(
name: $name,
summary: $summary,
prepTime: $prepTime,
prepTime: $cookTime,
totalTime: $totalTime,
servings: $servings
)
AddListData(list: $highlights, title: "Highlights", placeholderText: "Vegetarian")
AddListData(list: $directions, title: "Directions", placeholderText: "Simmer for an hour")
}
.padding(.horizontal)
}
}
In our sub-view for the form, we have several TextFields bound to the properties sent from the previous call:
import SwiftUI
struct AddMetaData: View {
@Binding var name: String
@Binding var summary: String
@Binding var prepTime: String
@Binding var cookTime: String
@Binding var totalTime: String
@Binding var servings: String
var body: some View {
Group {
HStack {
Text("Name: ")
.fontWeight(.bold)
TextField("Tuna Casserole", text: $name)
}
HStack {
Text("Summary: ")
.fontWeight(.bold)
TextField("A delicious meal for the whole family", text: $summary)
}
HStack {
Text("PrepTime: ")
.fontWeight(.bold)
TextField("1 hour", text: $prepTime)
}
HStack {
Text("Cook Time: ")
.fontWeight(.bold)
TextField("2 hours", text: $cookTime)
}
HStack {
Text("Total Time: ")
.fontWeight(.bold)
TextField("3 hours", text: $totalTime)
}
HStack {
Text("Servings: ")
.fontWeight(.bold)
TextField("6", text: $servings)
}
}
}
}
The main magic from Lesson 6 is adding to our list data
import SwiftUI
struct AddListData: View {
@Binding var list: [String]
@State private var item: String = ""
var title: String
var placeholderText: String
var body: some View {
VStack (alignment: .leading) {
HStack {
Text("\(title):")
.fontWeight(.bold)
TextField(placeholderText, text: $item)
Button("Add") {
if item.trimmingCharacters(in: .whitespacesAndNewlines) != "" {
list.append(item.trimmingCharacters(in: .whitespacesAndNewlines))
item = ""
}
}
}
ForEach(list, id: \.self) { item in
Text(item)
}
}
}
}
Two little insights that came up with this lesson.
Firstly, we need to ensure we initialise the private state property. Originally, we had this:
@State private var item: String
And that threw up this error:
... AddRecipeView.swift:51:21: 'AddListData' initializer is inaccessible due to 'private' protection level
It’s pretty logical really, in that the property must be initiated within its “private” scope.
Secondly, we originally used a List()
instead of ForEach()
, re:
List(list, id: \.self) { item in
Text(item)
}
We discovered that, although we are appending the data with list.append()
, this wasn’t being reflected back to highlights
and directions
in our main view and so was not triggering a screen refresh, and the list was not shown on-screen.
Changing to ForEach()
fixes that issue.
Lesson 7
This lesson continues in the same vein as the above, this time to allow the user to add ingredients to the form.
The main difference here is that we declare our binding property data type as our original Ingredient
class (now called IngredientJSON
), as that’s already set up from before.
struct AddIngredientData: View {
@Binding var ingredients: [IngredientJSON]
@State private var name = ""
@State private var unit = ""
@State private var num = ""
@State private var denom = ""
var body: some View {
VStack (alignment: .leading) {
Text("Ingredients:")
.fontWeight(.bold)
.padding(.top, 5)
HStack {
TextField("Sugar", text: $name)
TextField("1", text: $num)
.frame(width:20)
Text("/")
TextField("2", text: $denom)
.frame(width:20)
TextField("Grams", text: $unit)
Button("Add") {
let cleanedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
let cleanedNum = num.trimmingCharacters(in: .whitespacesAndNewlines)
let cleanedDenom = denom.trimmingCharacters(in: .whitespacesAndNewlines)
let cleanedUnit = unit.trimmingCharacters(in: .whitespacesAndNewlines)
if cleanedName == "" || cleanedNum == "" || cleanedDenom == "" || cleanedUnit == "" {
return
}
let i = IngredientJSON()
i.id = UUID()
i.name = cleanedName
i.num = Int(cleanedNum) ?? 1
i.denom = Int(cleanedDenom) ?? 1
i.unit = cleanedUnit
ingredients.append(i)
name = ""
unit = ""
num = ""
denom = ""
}
}
ForEach(ingredients) { ingredient in
Text("\(ingredient.name), \(ingredient.num ?? 1)/\(ingredient.denom ?? 1) \(ingredient.unit ?? "")")
}
}
}
}
And now we’re ready to move on to handling the image data. That’s going to be enlightening.