Search code examples
iosswiftuiparameters

Missing parameter but need parameter to be changed based on user input


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)")
            }
        }
    }
}
}

Solution

  • 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.