LoginSignup
1
1

More than 1 year has passed since last update.

【SwiftUI】Listにスッキリ収まる時間選択Pickerを作る

Posted at

概要

  • よくある、ホイールで時間選択するやつを作りたかった
  • しかも、ListのViewの中で使えるようなやつ
  • 「シンプルver.」と、「エラー付きver.」のやつと2種類作りました。
  • ここでは主に前者の仕組みを解説します。
  • 後者は、@AppStrageに保存される前に、バリデーションチェックしてくれる仕組みが入ってます。

環境

  • macOS: 13.0.1
  • iOS: 16.1
  • XCode: 14.1

完成品(2種類)

  1. シンプルな方
  1. エラー付きの方

GitHub

ソースコード1(シンプルな方)

  1. まず、時間選択する画面は以下のようになってます。保存される時間を@AppStrageで定義して、後述するSelectTimeWheelView()でバインドさせています。

    SelectTimeView.swift
        import 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)
            }
        }
    
  2. 次に、ホイールのSelectTimeWheelView()の部分です。最初に時間と分の配列を定義させています(以下の例は15分刻みです)。それをPicker使ってForEachで繰り返す感じ(配列をForEachで繰り返す際は id: \.selfを忘れずに)。

    SelectTimeWheelView.swift
    
    import 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(エラー付きの方)

  1. あまり解説しませんが、以下の通りです。一旦@Stateで状態変数に持たせてから、画面が遷移するタイミングで@AppStrageに保存させてます。で、その状態変数が変更されたタイミングでバリデーションチェックをかけてます(これでいいのかはあまり自信ありません・・・)。

ソースコード2(おまけ)

  1. 最後に一応ContentView()を載せときます。シンプルな方とエラー付きな方を見比べることができます。
    ContentView.swift
    import SwiftUI
    
    struct ContentView: View {
        
        var body: some View {
            NavigationStack {
                List {
                    NavigationLink{
                        SelectTimeView()
                    } label: {
                        Text("時間を選択(シンプル)")
                    }
                    NavigationLink{
                        SelectTimeViewWithError()
                    } label: {
                        Text("時間を選択(エラー付き)")
                    }
                }
                .navigationTitle("時間選択サンプル")
                .navigationBarTitleDisplayMode(.inline)
            }
        }
        
    }
    

以上

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1