🫥

Pickerのselectionの初期値をnilにしたい

2024/03/20に公開

小ネタです。Pickerで扱う値の初期値をnilにしたい場合にどうすれば扱えるか考えました

やりたいこと

いくつかの要素の中から一つを選択させたい場合 Picker の使用が候補に上がります。Picker(_:selection:content)やその他のinitializerに必要な selection部分の型は Binding<SelectValue> となっており SelectValue はHashableである必要があります。Pickerの content 部分はよくある書き方として ForEach が使われると思います。以下のようなコードです

enum Kind: String, CaseIterable {
  case one, two, three
}

@State var kind: Kind

Picker("", selection: $kind) {
  ForEach(Kind.allCases, id: \.self) { kind in
    Text(kind.rawValue).tag(kind)
  }
}

よく見るコードだと思います。one,two,threeのいずれかを選ばせたい場合のコードになります。初期値がone,two,threeのいずれかで済む場合はこれで良いのですが、困る場合は初期値は無しでユーザーに何かしら選ばせたい場合になると思います。とりあえず case none みたいなものを追加する案がすぐに思い浮かぶと思います。以下のような具合です

コード
import SwiftUI

enum Kind: String, CaseIterable {
  case none
  case one, two, three

  var text: String {
    switch self {
    case .none:
      return "Please Select"
    case _:
      return "\(self)"
    }
  }
}

struct ContentView: View {
  @State var kind: Kind = .none
  var body: some View {
    Picker("", selection: $kind) {
      ForEach(Kind.allCases, id: \.self) { kind in
        Text(kind.text).tag(kind)
      }
    }

    Button {
      print("Run")
    } label: {
      Text("Run")
    }
    .disabled(kind == .none)
  }
}

#Preview {
  ContentView()
}

ここで @State var kind: Kind? = nil で宣言できないかなあ。と思って考えた内容を次に書きます

解決策

.tag{N} as Kind? と書くことで行けます

コード
import SwiftUI

enum Kind: String, CaseIterable {
  case one, two, three
}

struct ContentView: View {
  @State var kind: Kind? = nil
  var body: some View {
    Picker("", selection: $kind) {
      Text("Please select").tag(nil as Kind?)
      ForEach(Kind.allCases, id: \.self) { kind in
        Text(kind.rawValue).tag(kind as Kind?)
      }
    }

    Button {
      print("Run")
    } label: {
      Text("Run")
    }
    .disabled(kind == nil)
  }
}

#Preview {
  ContentView()
}

とりあえずできました。ちなみに .tag(nil) はコンパイルエラーになります。そして、Optional<Wrapped>のWrappedがHashableならOptionalもHashableになります。なのでこの書き方でもコンパイルが通ります。こちらの書き方の利点はnilを扱えることで、よく無い点は kind as Kind? を書かずに .tag(kind) と書くと動かなくなる点です。

まとめ

ちょっとした発見だったので記事にしました。

どちらが良いか。っていうのは置いておいて、「そうかこれでいいんだな」って発見をしたので記事にしました。ちなみに私は Optional<Kind> にする時の kind as Kind? が勢いで消されそうなので採用を避ける or コメントを残して採用とかでしょう。ちなみに case noneの例の部分の none というワードも避けたい気持ちがあるので別の名前とかにしそうです。

まさかの記事の内容をすべて否定する結論を書いてしまいました。ですが、この考え方は他の場所でも活かす部分がありそうなのでこれからもこういう書き方ができたのか。っていう発見をして面白いのがあったら記事にします

おしまい \(^o^)/

Discussion