概要
- よくある、ホイールで時間選択するやつを作りたかった
- しかも、
List
のViewの中で使えるようなやつ - 「シンプルver.」と、「エラー付きver.」のやつと2種類作りました。
- ここでは主に前者の仕組みを解説します。
- 後者は、
@AppStrage
に保存される前に、バリデーションチェックしてくれる仕組みが入ってます。
環境
- macOS: 13.0.1
- iOS: 16.1
- XCode: 14.1
完成品(2種類)
- シンプルな方
- エラー付きの方
GitHub
ソースコード1(シンプルな方)
-
まず、時間選択する画面は以下のようになってます。保存される時間を
@AppStrage
で定義して、後述するSelectTimeWheelView()
でバインドさせています。SelectTimeView.swiftimport SwiftUI struct SelectTimeView: View { // 保存される時間 @AppStorage("start_hour1") var startHour : Int = 8 @AppStorage("start_min1") var startMin : Int = 15 @AppStorage("end_hour1") var endHour : Int = 17 @AppStorage("end_min1") var endMin : Int = 15 // Pickerを開け閉めするための状態変数 @State var isOpenStartSet: Bool = false @State var isOpenEndSet: Bool = false var body: some View{ List{ // 開始・終了セクション Section { // (1)開始ボタン Button { // アニメーションをつけてニュッと開く withAnimation { isOpenStartSet.toggle() isOpenEndSet = false } } label: { HStack { Text("開始") .foregroundColor(.primary) Spacer() Text("\(numToString(startHour)):\(numToString(startMin))") .foregroundColor(isOpenStartSet ? .blue : .secondary) } } // isOpenStartSetがtrueの時、表示 isOpenStartSet ? SelectTimeWheelView( hour: $startHour, minute: $startMin, isOpen: $isOpenStartSet) : nil // (2)終了ボタン Button { // アニメーションをつけてニュッと開く withAnimation { isOpenEndSet.toggle() isOpenStartSet = false } } label: { HStack { Text("終了") .foregroundColor(.primary) Spacer() Text("\(numToString(endHour)):\(numToString(endMin))") .foregroundColor(isOpenEndSet ? .blue : .secondary) } } // isOpenSEndSetがtrueの時、表示 isOpenEndSet ? SelectTimeWheelView( hour: $endHour, minute: $endMin, isOpen: $isOpenStartSet) : nil } Section(header:Text("ここに説明とか入れるとそれっぽいですね。").font(.caption)){} } // List }// body // Int型の"2"を"02"とか0埋めしてくれる処理 private func numToString(_ num: Int) -> String { return String(format: "%02d", num) } }
-
次に、ホイールの
SelectTimeWheelView()
の部分です。最初に時間と分の配列を定義させています(以下の例は15分刻みです)。それをPicker
使ってForEach
で繰り返す感じ(配列をForEachで繰り返す際はid: \.self
を忘れずに)。SelectTimeWheelView.swiftimport SwiftUI struct SelectTimeWheelView: View { // バインド変数 @Binding var hour: Int @Binding var minute: Int @Binding var isOpen: Bool // 時・分の選択肢 let HourList: [Int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] let minuteList: [Int] = [0, 15, 30, 45] var body: some View { HStack { // 時 Picker(selection: $hour) { ForEach(0..<Int(HourList.count), id: \.self) { index in Text(String(format: "%02d", HourList[index]) + "時") .tag(HourList[index]) } } label: {} // 分 Picker(selection: $minute) { ForEach(0..<Int(minuteList.count), id: \.self) { index in Text(String(format: "%02d", minuteList[index]) + "分") .tag(minuteList[index]) } } label: {} } // ホイールスタイル .pickerStyle(.wheel) } }
ソースコード2(エラー付きの方)
- あまり解説しませんが、以下の通りです。一旦
@State
で状態変数に持たせてから、画面が遷移するタイミングで@AppStrage
に保存させてます。で、その状態変数が変更されたタイミングでバリデーションチェックをかけてます(これでいいのかはあまり自信ありません・・・)。
ソースコード2(おまけ)
- 最後に一応
ContentView()
を載せときます。シンプルな方とエラー付きな方を見比べることができます。ContentView.swiftimport SwiftUI struct ContentView: View { var body: some View { NavigationStack { List { NavigationLink{ SelectTimeView() } label: { Text("時間を選択(シンプル)") } NavigationLink{ SelectTimeViewWithError() } label: { Text("時間を選択(エラー付き)") } } .navigationTitle("時間選択サンプル") .navigationBarTitleDisplayMode(.inline) } } }
以上
参考