swiftuiswiftui-picker

SwiftUI picker with TextField for optional entry


Picker with a choice of names. If ‘other’ is selected a TextField appears. User enters something into the TextField and the user entered value needs to also be reassigned to the Picker variable. I've searched and tried a 100 different options, nothing. (Also, I'm sure this isn't the best way, but don't know any better at this stage...) Thanks.

(Code simplified from actual)

import SwiftUI

struct ContentView: View {
    @State private var playerList = ["sally", "bob", "mary"]
    @State  private var player1 = "tester"
    @State  private var player1other = ""

    
    var body: some View {
        NavigationView{
            VStack{
                List{
                    Picker("Player 1:", selection: $player1) {
                        ForEach(playerList, id: \.self) {
                            Text($0)
                        }
                        Divider()
                        Text("Non-roster player").tag("other")
                    }
                    if player1 == "other" {
                       TextField("Player name", text: $player1other)
                    }
                    //Now need something here that updates player1 to be whatever player1other is
                    // something like player1 = player1other
                    //that doesn't create an error - Type '()' cannot conform to 'View'
                }
            }
        }
    }
}

Solution

  • Edit: I should also address that NoeOnJupiter is correct in that your attempt to add player1 = player1other doesn't work because that's not a SwiftUI View but a statement. SwiftUI is declarative so you can't throw functions (for lack of a better term) in the middle of where you're building your view unless a view uses it (i.e. a button or the .onChange modifier)


    One problem here is you have a static group of names that is supposed to be used to display the selected name (i.e. if the Picker selection is bob, bob will be shown by the Picker) but if there's a new name how will it be displayed by the Picker without being in the Picker's dataset?

    I recommend doing one of two things:

    Approach 1: Usage: Tap "sally", choose non-roster, type a name, submit. You'll notice that name gets added to the list.

    struct ContentView: View {
        @State private var playerList = ["sally", "bob", "mary"]
        @State  private var player1 = /**"tester"*/ "sally" // recommended by Swift Playgrounds to only use valid selections as a value for this  
        @State  private var player1other = ""
        
        
        var body: some View {
            NavigationView{
                VStack{
                    List{
                        Picker("Player 1:", selection: $player1) {
                            ForEach(playerList, id: \.self) {
                                Text($0)
                            }
                            Divider()
                            Text("Non-roster player").tag("other")
                        }
                        if player1 == "other" {
                            HStack {
                                TextField("Player name", text: $player1other)
                                Button("Submit") {
                                    playerList.append(player1other)
                                    player1 = player1other
                                    player1other = "" // reset for next selection
                                }
                            }
                        }
                        //Now need something here that updates player1 to be whatever player1other is
                        // something like player1 = player1other
                        //that doesn't create an error - Type '()' cannot conform to 'View'
                    }
                }
            }
        }
    }
    

    Approach 2: Usage: Tap "sally", choose non-roster, start typing a name in the text-field. You'll notice the player name at the top of the screen updates in real time but will not be saved if the user changes names again.

    struct ContentView: View {
        @State private var playerList = ["sally", "bob", "mary"]
        @State private var selectedFromList = "sally" // keeps track of picker selection, but not player
        @State  private var player1 = "tester"
        @State  private var player1other = ""
        
        
        var body: some View {
            NavigationView{
                VStack{
                    // Displays current player name
                    Text(player1)
                        .font(.largeTitle)
                    List{
                        Picker("Player 1:", selection: $selectedFromList) {
                            ForEach(playerList, id: \.self) {
                                Text($0)
                            }
                            Divider()
                            Text("Non-roster player").tag("other")
                        }
                        // NEW
                        .onChange(of: selectedFromList) { newValue in
                            if selectedFromList != "other" {
                                player1 = selectedFromList
                            }
                        }
                        // ENDNEW
                        if selectedFromList == "other" { // Edited for new var
                            HStack {
                                TextField("Player name", text: $player1other)
                                // NEW
                                    .onChange(of: player1other) { newValue in
                                        player1 = player1other
                                    }
                                //ENDNEW
                            }
                        }
                        //Now need something here that updates player1 to be whatever player1other is
                        // something like player1 = player1other
                        //that doesn't create an error - Type '()' cannot conform to 'View'
                    }
                }
            }
        }
    }