This file is for the main structure of the application. This is where the error is coming from which is "Missing argument for parameter 'numberOfDoors' in call". This is because it wants me to add
ContentView(numberOfDoors: <#Int#>)
but im having trouble finding out how I can get what the user chooses to be the int instead of me putting a number in there statically.
import SwiftUI
@main
struct NumOfDoorsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
This is my project file.
import SwiftUI
struct ContentView: View {
@State var numberOfDoors: Int
@State var multiOptions: Array<String>
init(numberOfDoors: Int) {
self.numberOfDoors = numberOfDoors
self.multiOptions = [String](repeating: "", count: numberOfDoors)
}
var body: some View {
NavigationView {
Form{
Section {
Picker("Number of doors", selection: $numberOfDoors) {
ForEach(1 ..< 64) {
Text("\($0) doors")
}
}
ForEach(multiOptions.indices, id: \.self) { index in
TextField("Enter your option...", text: $multiOptions[index])
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
Section {
Text("\(numberOfDoors 1)")
}
}
}
}
}
CodePudding user response:
You write an initializer with a parameter, namely number of doors. Your App now wants to call ContentView and therefore the View needs to be initialized. Since you defined the initializer to be called with a parameter, your App needs to call ContentView with a parameter numberOfDoors.
Looking at your code, I see that the user is allowed to pick the number of doors in the app. Therefore you should ask you whether contentView needs to be initialized with a parameter or you want to start ContentView with numberOfDoors with a initial value.
So, my question back, why do you need to initialize ContentView? And, can you not start ContentView with:
@State private var numberOfDoors = 1
?
Kind regards, McUserT
CodePudding user response:
One of the most important parts of SwiftUI programming is creating an appropriate model for your views.
@State
properties are OK for local, often independent properties, but they typically aren't a good solution for your main data model or where a model needs to be manipulated by the user.
In your case you want the size of the array to change based on the selected number of doors, so you need somewhere for that procedural code to live; The model is that place.
Here is a simple model object you can use
class DoorModel: ObservableObject {
@Published var numberOfDoors: Int {
didSet {
self.adjustArray(newSize: numberOfDoors)
}
}
@Published var doors:[String]
init(numberOfDoors: Int) {
self.numberOfDoors = numberOfDoors
self.doors = [String](repeating: "", count: numberOfDoors)
}
private func adjustArray(newSize: Int) {
let delta = newSize - doors.count
print("new size = \(newSize) Delta = \(delta)")
if delta > 0 {
doors.append(contentsOf:[String](repeating: "", count: delta))
} else if delta < 0 {
doors.removeLast(-delta)
}
}
}
Note that you still need to supply a starting number of doors via the initialiser for your model. Whenever that value changes, the didSet
property observer calls a function to add or remove elements from the end of the array.
You can use this model in your view with an @StateObject
decorator. This ensures that a single instance is created and reused as your view is redrawn
struct ContentView: View {
@StateObject var model = DoorModel(numberOfDoors: 1)
var body: some View {
NavigationView {
Form{
Section {
Picker("Number of doors", selection: $model.numberOfDoors) {
ForEach(1 ..< 64) { index in
Text("\(index) doors").tag(index)
}
}
ForEach($model.doors.indices, id: \.self) { index in
TextField("Enter your option...", text: $model.doors[index])
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
}
}
}
}
I added the .tag
modifier to ensure that the picker works correctly with your 1-based list; By default the tag will be 0 based.