Reputation: 244
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
but in iOS 16, the same code leads to different results, when selection is nil it has a default value
Upvotes: 11
Views: 7375
Reputation: 815
My version of this problem involves letting the user go back from selection a value to selecting no value when you want the picker to work directly with your custom types. Other solutions work with generic Int or String by setting the value to 0 or a visibly empty string (e.g., " "). You can't do that with custom types, though.
The secret lies in using nil
as your tag, and casting it to your custom type with nil as CustomType?
(note that you have to make it optional, with ?
).
Here is a fully working example:
import SwiftUI
import SwiftData
@Model
class SampleItem: Identifiable {
var id = UUID().uuidString
var name: String
init(_ name: String) {
self.name = name
}
}
struct ExampleView: View {
var values: [SampleItem] = [
SampleItem("John"),
SampleItem("Paul"),
SampleItem("George"),
SampleItem("Ringo")
]
@State var selectedValue: SampleItem?
var body: some View {
Picker("Select a value", selection: $selectedValue) {
Text("None")
.tag(nil as SampleItem?)
ForEach(values, id: \.self) {
Text($0.name).tag($0)
}
}
}
}
Upvotes: 0
Reputation: 31
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)
}
}
}
Upvotes: 0
Reputation: 61
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
Upvotes: 5
Reputation: 1091
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")")
}
}
}
}
}
Upvotes: 24