darkenney
darkenney

Reputation: 37

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'
                }
            }
        }
    }
}

Upvotes: 0

Views: 1996

Answers (2)

RelativeJoe
RelativeJoe

Reputation: 5104

The reason you're getting

Type '()' cannot conform to 'View'

is because body is a ViewBuilder. Meaning it expects any type conforming to View, whereas player1 = player1other is of type Void != View.

Here's what you should do, depending on what you need:

  1. Changing player1 to player1other when the user presses return (Recommended):

    TextField("Player name", text: $player1other)
        .onSubmit {
            player1 = player1other
        }
    
  2. Directly assigning the text of TextField to be player1:

    TextField("Player name", text: $player1)
    
  3. Realtime updating player1other from player1:

    TextField("Player name", text: $player1other)
        .onChange(of: player1other) { newValue in
            player1 = newValue
        }
    

Upvotes: 0

Tyler Dakin
Tyler Dakin

Reputation: 65

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:

  • Add new names to the dataset
  • Have a different Text element to display the player1 name

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'
                }
            }
        }
    }
}

Upvotes: 0

Related Questions