Normalement, je peux afficher une liste d'éléments comme celui-ci dans SwiftUI:
struct FruitView: View { @State private var fruit: Fruit? var body: some View { Picker(selection: $fruit, label: Text("Fruit")) { ForEach(Fruit.allCases) { fruit in Text(fruit.rawValue).tag(fruit) } } } }
Cela fonctionne parfaitement, me permettant de sélectionner le fruit que je veux. Si je veux changer de fruit
pour qu'il soit nullable (c'est-à-dire facultatif), cela pose des problèmes:
enum Fruit { case apple case orange case banana } struct FruitView: View { @State private var fruit = Fruit.apple var body: some View { Picker(selection: $fruit, label: Text("Fruit")) { ForEach(Fruit.allCases) { fruit in Text(fruit.rawValue).tag(fruit) } } } }
Le nom du fruit sélectionné n'est plus affiché sur le premier écran, et quel que soit l'élément de sélection que je choisis, il ne met pas à jour la valeur du fruit.
Comment utiliser Picker avec un type facultatif?
4 Réponses :
La balise doit correspondre au type de données exact lors de l'encapsulation de la liaison. Dans ce cas, le type de données fourni pour tag
est Fruit
mais le type de données $fruit.wrappedValue
est Fruit?
. Vous pouvez résoudre ce problème en convertissant le type de données dans la méthode de tag
:
struct FruitView: View { @State private var fruit: Fruit? var body: some View { Picker(selection: $fruit, label: Text("Fruit")) { Text("No fruit").tag(nil as Fruit?) ForEach(Fruit.allCases) { fruit in Text(fruit.rawValue).tag(fruit as Fruit?) } } } }
Bonus : Si vous voulez un texte personnalisé pour nil
(au lieu de simplement vide), et voulez que l'utilisateur soit autorisé à sélectionner nil
(Remarque: c'est tout ou rien ici), vous pouvez inclure un élément pour nil
:
struct FruitView: View { @State private var fruit: Fruit? var body: some View { Picker(selection: $fruit, label: Text("Fruit")) { ForEach(Fruit.allCases) { fruit in Text(fruit.rawValue).tag(fruit as Fruit?) } } } }
N'oubliez pas de lancer la valeur nil
également.
Quelques variations de cette syntaxe: .tag(nil as Fruit?)
, .tag(Fruit?.none)
, .tag(Fruit?(nil))
.tag(Optional<Fruit>.none)
.tag(Fruit?(nil))
, .tag(Optional<Fruit>.none)
. 🙂
Pourquoi ne pas étendre l'énumération avec une valeur par défaut? Si ce n'est pas ce que vous essayez d'atteindre, peut-être pouvez-vous également fournir des informations sur les raisons pour lesquelles vous voulez que cela soit optional
.
enum Fruit: String, CaseIterable, Hashable { case apple = "apple" case orange = "orange" case banana = "banana" case noValue = "" } struct ContentView: View { @State private var fruit = Fruit.noValue var body: some View { VStack{ Picker(selection: $fruit, label: Text("Fruit")) { ForEach(Fruit.allCases, id:\.self) { fruit in Text(fruit.rawValue) } } Text("Selected Fruit: \(fruit.rawValue)") } } }
Votre commentaire est assez juste pour une enum
, mais que se passe-t-il s'il s'agissait d'une sélection à partir d'un tableau ou d'un ensemble où «aucune des réponses ci-dessus» pourrait être un choix valide? La réponse de @ Senseful est alors très utile!
J'ai fait un repo public ici avec la solution de Senseful: https://github.com/andrewthedina/SwiftUIPickerWithOptionalSelection
EDIT: Merci pour les commentaires concernant la publication de liens. Voici le code qui répond à la question. Le copier / coller fera l'affaire, ou clonera le dépôt à partir du lien.
import SwiftUI struct ContentView: View { @State private var selectionOne: String? = nil @State private var selectionTwo: String? = nil let items = ["Item A", "Item B", "Item C"] var body: some View { NavigationView { Form { // MARK: - Option 1: NIL by SELECTION Picker(selection: $selectionOne, label: Text("Picker with option to select nil item [none]")) { Text("[none]").tag(nil as String?) .foregroundColor(.red) ForEach(items, id: \.self) { item in Text(item).tag(item as String?) // Tags must be cast to same type as Picker selection } } // MARK: - Option 2: NIL by BUTTON ACTION Picker(selection: $selectionTwo, label: Text("Picker with Button that removes selection")) { ForEach(items, id: \.self) { item in Text(item).tag(item as String?) // Tags must be cast to same type as Picker selection } } if selectionTwo != nil { // "Remove item" button only appears if selection is not nil Button("Remove item") { self.selectionTwo = nil } } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Veuillez ne pas simplement publier des liens vers une réponse. Postez le code correspondant et le lien. De cette façon, votre réponse ne disparaît pas et la valeur qu'elle aurait pu fournir est perdue.
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change. - De l'avis
J'apprécie cela, messieurs (@Andrew, @kenny_k). Édité!
En fait, je préfère la solution de @ Senseful pour une solution ponctuelle, mais pour la postérité: vous pouvez également créer une énumération de wrapper, qui si vous avez une tonne de types d'entités dans votre application, évolue assez bien via des extensions de protocole.
Picker(selection: $task.job, label: Text("Job")) { ForEach(Model.shared.jobs.optionalPickable) { p in p.itemView } }
L'utilisation est alors assez serrée:
// utility constraint to ensure a default id can be produced protocol EmptyInitializable { init() } // primary constraint on PickerValue wrapper protocol Pickable { associatedtype Element: Identifiable where Element.ID: EmptyInitializable } // wrapper to hide optionality enum PickerValue<Element>: Pickable where Element: Identifiable, Element.ID: EmptyInitializable { case none case some(Element) } // hashable & equtable on the wrapper extension PickerValue: Hashable & Equatable { func hash(into hasher: inout Hasher) { hasher.combine(id) } static func ==(lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id } } // common identifiable types extension String: EmptyInitializable {} extension Int: EmptyInitializable {} extension UInt: EmptyInitializable {} extension UInt8: EmptyInitializable {} extension UInt16: EmptyInitializable {} extension UInt32: EmptyInitializable {} extension UInt64: EmptyInitializable {} extension UUID: EmptyInitializable {} // id producer on wrapper extension PickerValue: Identifiable { var id: Element.ID { switch self { case .some(let e): return e.id case .none: return Element.ID() } } } // utility extensions on Array to wrap into PickerValues extension Array where Element: Identifiable, Element.ID: EmptyInitializable { var pickable: Array<PickerValue<Element>> { map { .some($0) } } var optionalPickable: Array<PickerValue<Element>> { [.none] + pickable } } // benefit of wrapping with protocols is that item views can be common // across data sets. (Here TitleComponent { var title: String { get }}) extension PickerValue where Element: TitleComponent { @ViewBuilder var itemView: some View { Group { switch self { case .some(let e): Text(e.title) case .none: Text("None") .italic() .foregroundColor(.accentColor) } } .tag(self) } }
pouvez-vous partager votre classe / structure
Fruit
@SimonBachmann: c'est une énumération, a mis à jour la question