SwiftUI Picker with option for no selection in iOS16
Asked Answered
G

3

11

I am using Picker with option for no selection, in iOS15 it is working fine, but in iOS16 it has a default value, how can I remove this default value, I don't need to show the text to the right of the Picker line when selection is nil.

struct ContentView: View {
    
    @State private var selection: String?
    let strengths = ["Mild", "Medium", "Mature"]
    
    var body: some View {
        NavigationView {
            List {
                Section {
                    Picker("Strength", selection: $selection) {
                        ForEach(strengths, id: \.self) {
                            Text($0).tag(Optional($0))
                        }
                    }
                }
            }
        }
    }
}

in iOS15, when selection is nil, no text is displayed on the right side of the Picker row
enter image description here

but in iOS 16, the same code leads to different results, when selection is nil it has a default value enter image description here

Gardol answered 30/9, 2022 at 2:57 Comment(0)
E
23

Xcode 14.1 Beta 3 logs: "Picker: the selection "nil" is invalid and does not have an associated tag, this will give undefined results."

To resolve this log you need to add an Option which uses the nil tag.

struct ContentView: View {

    @State private var selection: String?
    let strengths = ["Mild", "Medium", "Mature"]

    var body: some View {
        NavigationView {
            List {
                Section {
                    Picker("Strength", selection: $selection) {
                        Text("No Option").tag(Optional<String>(nil))
                        ForEach(strengths, id: \.self) {
                            Text($0).tag(Optional($0))
                        }
                    }
                    Text("current selection: \(selection ?? "none")")
                }
            }
        }
    }
}
Euripus answered 30/9, 2022 at 6:36 Comment(2)
Optional<String>(nil) can be String?(nil)Alphonsealphonsine
The Text($0).tag(Optional($0)) part of this had me stumped for weeks. It's absurd that SwiftUI fails silently to select the item if you don't wrap it in Optional.Thetes
T
5

This is what I ended up doing for iOS 16.0 from XCode 14.0.1 (to avoid user irritation on iOS 16.0 devices):

let promptText: String = "select" // just a default String
    
//short one for your example
Section {
    Picker("Strength", selection: $selection) {
        if selection == nil { // this will work, since there is no initialization to the optional value in your example
            Text(promptText).tag(Optional<String>(nil)) // is only shown until a selection is made
        }
        ForEach(strengths, id: \.self) {
            Text($0).tag(Optional($0))
        }
    }
}
    
// more universal example
Section {
    Picker("Strength", selection: $selection) {
        if let safeSelection = selection{
            if !strengths.contains(safeSelection){ // does not care about a initialization value as long as it is not part of the collection 'strengths'
                Text(promptText).tag(Optional<String>(nil)) // is only shown until a selection is made
            }
        }else{
            Text(promptText).tag(Optional<String>(nil))
        }
        ForEach(strengths, id: \.self) {
            Text($0).tag(Optional($0))
        }
    }
}
    
// Don't want to see anything if nothing is selected? empty String "" leads to an warning. Go with non visual character like " " or 'Horizontal Tab'. But then you will get an empty row...
Section {
    let charHorizontalTab: String = String(Character(UnicodeScalar(9)))
    Picker("Strength", selection: $selection) {
        if let safeSelection = selection{
            if !strengths.contains(safeSelection){ // does not care about a initialization value as long as it is not part of the collection 'strengths'
                Text(charHorizontalTab).tag(Optional<String>(nil)) // is only shown until a selection is made
            }
        }else{
            Text(charHorizontalTab).tag(Optional<String>(nil))
        }
        ForEach(strengths, id: \.self) {
            Text($0).tag(Optional($0))
        }
    }
}

good luck finding a solution that works for you

Thompson answered 18/10, 2022 at 18:5 Comment(0)
L
0

This is how I solved it:

@State private var showAddNewUserWindow = false
@State private var newUser: String = ""

@State private var selectedUser = "Item 1"
@State private var usersList = ["Item 1", "Item 2", "Item 3", "Item 4"]
@State private var isPickerDisabled = false

Button("—") {
    if let index = usersList.firstIndex(of: selectedUser) {
        usersList.remove(at: index)
        if usersList.count == 0 {
            usersList.insert("No user", at: 0)
            isPickerDisabled = true
        }
        selectedUser = usersList.first!
    }
}
                
Picker("",selection:$selectedUser) {
    ForEach(usersList, id: \.self) {
        Text($0)
    }
                    
}
.disabled(isPickerDisabled)
.onChange(of: selectedUser) {_ in
    usersList.remove(at: usersList.firstIndex(of: selectedUser)!)
    usersList.insert(selectedUser, at: 0)
}
                
Button("+") {
    showAddNewUserWindow = true
}
.popover(isPresented: $showAddNewUserWindow) {
    VStack {
        Spacer()
            .frame(height: 26.0)
                        
        Text("Add new user")
            .font(.title2)
                        
        Spacer()
            .frame(height: 6.0)
                        
        Text("Please enter the name of the new user")
            .font(.caption)
                        
        Spacer()
            .frame(height: 20.0)
                        
        HStack {
            Spacer()
                .frame(width: 24.0)
                            
            TextField("New user's name", text: $newUser)
                .textFieldStyle(.roundedBorder)
                            
            Spacer()
                .frame(width: 24.0)
        }
                        
        Spacer()
            .frame(height: 20.0)
                        
        HStack {
            Button("Cancel") {
                showAddNewUserWindow.toggle()
            }
            .frame(width: 145.0, height: 50.0)
            .border(.quaternary, width: 0.8)
                            
            Button("Add") {
                if newUser.count > 0 {
                    if usersList.contains(newUser) == false {
                        if usersList.first == "No user" {
                            usersList.removeFirst()
                            isPickerDisabled = false
                        }
                                        
                        usersList.insert(newUser, at: 0)
                        selectedUser = newUser
                        newUser = ""
                        showAddNewUserWindow.toggle()
                    } else {
                                        
                    }
                } else {
                                    
                }
            }
            .frame(width: 145.0, height: 50.0)
            .border(.quaternary, width: 0.8)
        }
    }        
}
Leucocytosis answered 14/8, 2023 at 1:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.