MENU

【SwiftUI入門】DatePickerの使い方完全ガイド!日付選択を実装する方法

アプリ開発で避けて通れないのが、日付や時刻の入力機能です。

「予約アプリで日付を選択させたい」 「誕生日の入力欄を作りたい」 「イベントの開始時刻を設定できるようにしたい」

こんな要望、よくありますよね。

SwiftUIのDatePickerを使えば、カレンダーUIを自分で実装することなく、iOS標準の使いやすい日付選択機能を簡単に追加できます。

この記事では、SwiftUI初心者の方に向けて、DatePickerの基本から実践的な使い方まで、サンプルコードとともにわかりやすく解説します。

目次

DatePickerとは?

DatePickerは、SwiftUIで日付や時刻をユーザーに選択してもらうためのコンポーネントです。

iOS標準のカレンダーUIやホイール型のピッカーを使って、直感的な日付入力を実現できます。

DatePickerでできること

  • 日付の選択(年月日)
  • 時刻の選択(時分)
  • 日付と時刻の両方を選択
  • 選択可能な範囲の制限
  • 様々な表示スタイルの切り替え

なぜDatePickerを使うべきか

自分でカレンダーUIを実装しようとすると、以下のような課題があります。

  • カレンダーのレイアウト実装が複雑
  • 日付の計算処理が面倒
  • ユーザビリティの確保が難しい
  • iOS標準との一貫性が保てない

DatePickerを使えば、これらの問題をすべて解決できます。

DatePickerの基本的な使い方

最もシンプルなDatePickerは、以下のように書けます。

import SwiftUI

struct BasicDatePickerView: View {
    @State private var selectedDate = Date()
    
    var body: some View {
        VStack {
            DatePicker("日付を選択", selection: $selectedDate)
                .padding()
            
            Text("選択された日付: \(selectedDate.formatted())")
                .padding()
        }
    }
}

コードの解説

  1. @State private var selectedDate = Date()
    • 選択された日付を保持する状態変数
    • Date()で現在の日時が初期値になる
  2. selection: $selectedDate
    • $を付けてバインディング
    • DatePickerで選択された値が自動的にselectedDateに反映される
  3. “日付を選択”
    • DatePickerの左側に表示されるラベル

DatePickerの必須パラメータ

DatePickerには、2つの必須パラメータがあります。

1. ラベル(第1引数)

DatePicker("ラベル", selection: $date)

ラベルを非表示にしたい場合は、空文字と.labelsHidden()を使います。

DatePicker("", selection: $date)
    .labelsHidden()

2. selection(バインディング)

選択された日付を保持する変数とのバインディングです。

@State private var date = Date()

var body: some View {
    DatePicker("日付", selection: $date)
}

重要: $を忘れるとエラーになります。

displayedComponents:表示要素の指定

displayedComponentsパラメータで、日付・時刻・両方のどれを表示するか指定できます。

日付のみ表示

年月日だけを選択させたい場合に使います。

@State private var date = Date()

DatePicker(
    "日付",
    selection: $date,
    displayedComponents: .date
)

使用例:

  • 誕生日の入力
  • 予約日の選択
  • イベント開催日

時刻のみ表示

時間と分だけを選択させたい場合に使います。

@State private var time = Date()

DatePicker(
    "時刻",
    selection: $time,
    displayedComponents: .hourAndMinute
)

使用例:

  • アラームの設定
  • 営業時間の設定
  • 予約時刻の選択

日付と時刻の両方(デフォルト)

何も指定しない場合、日付と時刻の両方が表示されます。

@State private var datetime = Date()

DatePicker("日時", selection: $datetime)
// または
DatePicker("日時", selection: $datetime, displayedComponents: [.date, .hourAndMinute])

使用例:

  • イベントの開始日時
  • 予約の日時
  • 締切日時

in:選択可能な範囲を制限する

inパラメータを使うと、選択できる日付の範囲を制限できます。

今日以降のみ選択可能

予約日など、未来の日付だけを選ばせたい場合に使います。

@State private var reservationDate = Date()

DatePicker(
    "予約日",
    selection: $reservationDate,
    in: Date()...,
    displayedComponents: .date
)

Date()...は「今日から無限の未来まで」を意味します。

過去の日付のみ選択可能

誕生日など、過去の日付だけを選ばせたい場合に使います。

@State private var birthday = Date()

DatePicker(
    "生年月日",
    selection: $birthday,
    in: ...Date(),
    displayedComponents: .date
)

...Date()は「無限の過去から今日まで」を意味します。

特定の期間のみ選択可能

期間限定のキャンペーンなど、特定の範囲だけを選ばせたい場合に使います。

@State private var campaignDate = Date()

// 今日から30日後まで
let endDate = Calendar.current.date(byAdding: .day, value: 30, to: Date())!

DatePicker(
    "キャンペーン期間",
    selection: $campaignDate,
    in: Date()...endDate,
    displayedComponents: .date
)

特定の年齢範囲を設定

例えば、18歳以上100歳以下という制限を設けることもできます。

@State private var birthday = Date()

var dateRange: ClosedRange<Date> {
    let calendar = Calendar.current
    let now = Date()
    
    // 100歳前の日付
    let minDate = calendar.date(byAdding: .year, value: -100, to: now)!
    // 18歳前の日付
    let maxDate = calendar.date(byAdding: .year, value: -18, to: now)!
    
    return minDate...maxDate
}

DatePicker(
    "生年月日(18歳以上)",
    selection: $birthday,
    in: dateRange,
    displayedComponents: .date
)

DatePickerのスタイル

iOS 14以降、DatePickerには4つの表示スタイルがあります。

1. automatic(デフォルト)

環境に応じて自動的に最適なスタイルが選択されます。

DatePicker("日付", selection: $date)
    .datePickerStyle(.automatic)

特徴:

  • システムが自動的に判断
  • 通常はcompactスタイルになる
  • 特に指定がなければこれを使う

2. compact(コンパクト)

タップするとモーダルでカレンダーが開くスタイル。

DatePicker("日付", selection: $date)
    .datePickerStyle(.compact)

特徴:

  • 省スペースで配置できる
  • タップするとカレンダーが表示される
  • iOS 14以降で利用可能

使用場面:

  • Formの中で使う場合
  • 縦方向のスペースを節約したい場合
  • 複数の入力項目がある画面

3. wheel(ホイール)

従来のiOSでよく見るドラム式のホイールスタイル。

DatePicker("日付", selection: $date)
    .datePickerStyle(.wheel)

特徴:

  • 縦方向のスペースを取る
  • 直感的に操作できる
  • iOS標準のピッカーと同じ見た目

使用場面:

  • 誕生日入力など、じっくり選ばせたい場合
  • モーダル画面で大きく表示する場合
  • 従来のiOSの操作感を重視する場合

4. graphical(グラフィカル)

インラインでカレンダーが常時表示されるスタイル。

DatePicker("日付", selection: $date)
    .datePickerStyle(.graphical)

特徴:

  • カレンダーが常時表示される
  • 月全体を見渡せる
  • 縦方向に大きなスペースが必要
  • iOS 14以降で利用可能

使用場面:

  • イベント作成画面
  • カレンダーアプリのような画面
  • 日付を視覚的に確認させたい場合

スタイルの比較表

スタイルスペース操作性適している場面
automatic普通一般的な用途
compact良いForm内、省スペース
wheel非常に良いモーダル、誕生日入力
graphical非常に大良いカレンダーアプリ

実践例1:予約フォームを作る

ホテルの予約フォームなど、実用的な例を見てみましょう。

struct ReservationForm: View {
    @State private var checkInDate = Date()
    @State private var checkOutDate = Date()
    @State private var arrivalTime = Date()
    @State private var guestName = ""
    
    // チェックアウトはチェックインの翌日以降
    var checkOutRange: PartialRangeFrom<Date> {
        Calendar.current.date(byAdding: .day, value: 1, to: checkInDate)!...
    }
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("ゲスト情報")) {
                    TextField("お名前", text: $guestName)
                }
                
                Section(header: Text("宿泊日程")) {
                    DatePicker(
                        "チェックイン",
                        selection: $checkInDate,
                        in: Date()...,
                        displayedComponents: .date
                    )
                    
                    DatePicker(
                        "チェックアウト",
                        selection: $checkOutDate,
                        in: checkOutRange,
                        displayedComponents: .date
                    )
                }
                
                Section(header: Text("到着予定時刻")) {
                    DatePicker(
                        "時刻",
                        selection: $arrivalTime,
                        displayedComponents: .hourAndMinute
                    )
                }
                
                Section {
                    Button("予約する") {
                        // 予約処理
                        print("予約: \(guestName)")
                        print("チェックイン: \(checkInDate)")
                        print("チェックアウト: \(checkOutDate)")
                    }
                    .disabled(guestName.isEmpty)
                }
            }
            .navigationTitle("ホテル予約")
        }
    }
}

ポイント解説

  1. 日付の連動
    • チェックアウトはチェックインの翌日以降に制限
    • checkOutRangeで動的に範囲を計算
  2. 入力検証
    • 名前が空の場合はボタンを無効化
    • .disabled()で実装
  3. Formとの組み合わせ
    • Sectionで項目をグループ化
    • 見やすく整理された画面

実践例2:誕生日入力画面を作る

年齢制限のある誕生日入力の例です。

struct BirthdayInputView: View {
    @State private var birthday = Date()
    @State private var showAlert = false
    
    // 18歳以上、100歳以下
    var dateRange: ClosedRange<Date> {
        let calendar = Calendar.current
        let now = Date()
        let minDate = calendar.date(byAdding: .year, value: -100, to: now)!
        let maxDate = calendar.date(byAdding: .year, value: -18, to: now)!
        return minDate...maxDate
    }
    
    // 年齢を計算
    var age: Int {
        let calendar = Calendar.current
        let ageComponents = calendar.dateComponents([.year], from: birthday, to: Date())
        return ageComponents.year ?? 0
    }
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("生年月日")) {
                    DatePicker(
                        "誕生日",
                        selection: $birthday,
                        in: dateRange,
                        displayedComponents: .date
                    )
                    .datePickerStyle(.wheel)
                }
                
                Section(header: Text("確認")) {
                    HStack {
                        Text("選択された日付")
                        Spacer()
                        Text(birthday.formatted(date: .long, time: .omitted))
                            .foregroundColor(.secondary)
                    }
                    
                    HStack {
                        Text("年齢")
                        Spacer()
                        Text("\(age)歳")
                            .foregroundColor(.secondary)
                    }
                }
                
                Section {
                    Button("登録") {
                        showAlert = true
                    }
                }
            }
            .navigationTitle("プロフィール登録")
            .alert("確認", isPresented: $showAlert) {
                Button("キャンセル", role: .cancel) { }
                Button("登録") {
                    // 登録処理
                    print("誕生日を登録: \(birthday)")
                }
            } message: {
                Text("この内容で登録しますか?")
            }
        }
    }
}

ポイント解説

  1. 年齢制限
    • 18歳以上100歳以下に制限
    • dateRangeで計算
  2. 年齢の自動計算
    • Calendar.dateComponentsを使用
    • リアルタイムで年齢を表示
  3. 確認機能
    • アラートで登録前に確認
    • ユーザビリティの向上

実践例3:イベント作成画面を作る

カレンダーを大きく表示するイベント作成の例です。

struct EventCreationView: View {
    @State private var eventName = ""
    @State private var startDate = Date()
    @State private var endDate = Date()
    @State private var allDay = false
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("イベント情報")) {
                    TextField("イベント名", text: $eventName)
                    Toggle("終日", isOn: $allDay)
                }
                
                Section(header: Text("開始")) {
                    DatePicker(
                        "開始日時",
                        selection: $startDate,
                        in: Date()...,
                        displayedComponents: allDay ? .date : [.date, .hourAndMinute]
                    )
                    .datePickerStyle(.graphical)
                }
                
                Section(header: Text("終了")) {
                    DatePicker(
                        "終了日時",
                        selection: $endDate,
                        in: startDate...,
                        displayedComponents: allDay ? .date : [.date, .hourAndMinute]
                    )
                    .datePickerStyle(.compact)
                }
                
                Section {
                    Button("イベントを作成") {
                        // 作成処理
                        print("イベント名: \(eventName)")
                        print("開始: \(startDate)")
                        print("終了: \(endDate)")
                    }
                    .disabled(eventName.isEmpty)
                }
            }
            .navigationTitle("イベント作成")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

ポイント解説

  1. 終日切り替え
    • Toggleで終日イベントに対応
    • 終日の場合は時刻選択を非表示
  2. 日時の連動
    • 終了日時は開始日時以降に制限
    • in: startDate...で実装
  3. スタイルの使い分け
    • 開始日時:graphical(視認性重視)
    • 終了日時:compact(省スペース)

日付のフォーマット表示

選択された日付を見やすく表示する方法です。

iOS 15以降の方法(推奨)

struct DateFormatView: View {
    @State private var date = Date()
    
    var body: some View {
        VStack(spacing: 20) {
            DatePicker("日付", selection: $date)
            
            Divider()
            
            // 様々なフォーマット
            Text("デフォルト")
            Text(date.formatted())
            
            Text("日付のみ")
            Text(date.formatted(date: .long, time: .omitted))
            
            Text("時刻のみ")
            Text(date.formatted(date: .omitted, time: .shortened))
            
            Text("年月日")
            Text(date.formatted(.dateTime.year().month().day()))
            
            Text("時分")
            Text(date.formatted(.dateTime.hour().minute()))
            
            Text("曜日付き")
            Text(date.formatted(.dateTime.year().month().day().weekday()))
        }
        .padding()
    }
}

主なフォーマットスタイル

// 日付スタイル
.formatted(date: .numeric, time: .omitted)    // 2024/1/15
.formatted(date: .abbreviated, time: .omitted) // 2024年1月15日
.formatted(date: .long, time: .omitted)       // 2024年1月15日
.formatted(date: .complete, time: .omitted)   // 2024年1月15日 月曜日

// 時刻スタイル
.formatted(date: .omitted, time: .shortened)  // 14:30
.formatted(date: .omitted, time: .standard)   // 14:30:00
.formatted(date: .omitted, time: .complete)   // 14時30分00秒 JST

DateFormatterを使う方法(従来)

より細かい制御が必要な場合は、DateFormatterを使います。

struct CustomDateFormat: View {
    @State private var date = Date()
    
    var formattedDate: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy年MM月dd日(E) HH:mm"
        formatter.locale = Locale(identifier: "ja_JP")
        return formatter.string(from: date)
    }
    
    var body: some View {
        VStack {
            DatePicker("日時", selection: $date)
            Text("カスタムフォーマット: \(formattedDate)")
        }
        .padding()
    }
}

主なフォーマット記号:

  • yyyy:4桁の年
  • MM:2桁の月
  • dd:2桁の日
  • E:曜日(短縮形)
  • HH:24時間制の時
  • mm:分
  • ss:秒

日付の計算と操作

選択された日付に対して計算を行う方法です。

日付の加算・減算

struct DateCalculationView: View {
    @State private var baseDate = Date()
    
    // 7日後
    var weekLater: Date {
        Calendar.current.date(byAdding: .day, value: 7, to: baseDate) ?? baseDate
    }
    
    // 1ヶ月前
    var monthAgo: Date {
        Calendar.current.date(byAdding: .month, value: -1, to: baseDate) ?? baseDate
    }
    
    // 1年後
    var yearLater: Date {
        Calendar.current.date(byAdding: .year, value: 1, to: baseDate) ?? baseDate
    }
    
    var body: some View {
        Form {
            Section(header: Text("基準日")) {
                DatePicker("日付", selection: $baseDate, displayedComponents: .date)
            }
            
            Section(header: Text("計算結果")) {
                HStack {
                    Text("7日後")
                    Spacer()
                    Text(weekLater.formatted(date: .long, time: .omitted))
                        .foregroundColor(.secondary)
                }
                
                HStack {
                    Text("1ヶ月前")
                    Spacer()
                    Text(monthAgo.formatted(date: .long, time: .omitted))
                        .foregroundColor(.secondary)
                }
                
                HStack {
                    Text("1年後")
                    Spacer()
                    Text(yearLater.formatted(date: .long, time: .omitted))
                        .foregroundColor(.secondary)
                }
            }
        }
    }
}

2つの日付間の日数を計算

struct DateDifferenceView: View {
    @State private var startDate = Date()
    @State private var endDate = Date()
    
    var daysBetween: Int {
        let calendar = Calendar.current
        let components = calendar.dateComponents([.day], from: startDate, to: endDate)
        return components.day ?? 0
    }
    
    var body: some View {
        Form {
            Section(header: Text("期間")) {
                DatePicker("開始日", selection: $startDate, displayedComponents: .date)
                DatePicker("終了日", selection: $endDate, in: startDate..., displayedComponents: .date)
            }
            
            Section(header: Text("結果")) {
                HStack {
                    Text("日数")
                    Spacer()
                    Text("\(daysBetween)日間")
                        .foregroundColor(.blue)
                        .bold()
                }
            }
        }
    }
}

DatePickerのカスタマイズ

DatePickerの見た目をカスタマイズする方法です。

アクセントカラーの変更

DatePicker("日付", selection: $date)
    .datePickerStyle(.graphical)
    .accentColor(.pink)  // 選択色を変更

背景とボーダーの追加

DatePicker("日付", selection: $date)
    .datePickerStyle(.compact)
    .padding()
    .background(
        RoundedRectangle(cornerRadius: 10)
            .fill(Color.gray.opacity(0.1))
    )
    .overlay(
        RoundedRectangle(cornerRadius: 10)
            .stroke(Color.blue, lineWidth: 1)
    )

ラベルのカスタマイズ

HStack {
    Image(systemName: "calendar")
        .foregroundColor(.blue)
    DatePicker("予約日", selection: $date)
}

よくあるエラーと対処法

エラー1: バインディングの$を忘れる

// ❌ 間違い
DatePicker("日付", selection: date)

// ✅ 正解
DatePicker("日付", selection: $date)

エラー2: Dateの初期化忘れ

// ❌ 間違い
@State private var date: Date

// ✅ 正解
@State private var date = Date()

エラー3: 範囲指定の順序ミス

// ❌ 間違い:終了日が開始日より前
let range = endDate...startDate

// ✅ 正解
let range = startDate...endDate

エラー4: displayedComponentsの指定ミス

// ❌ 間違い:存在しないコンポーネント
displayedComponents: .time

// ✅ 正解
displayedComponents: .hourAndMinute

実践的なTips

Tip 1: 営業時間の範囲を設定

特定の時間帯のみ選択可能にする方法です。

struct BusinessHourPicker: View {
    @State private var appointmentTime = Date()
    
    var timeRange: ClosedRange<Date> {
        let calendar = Calendar.current
        let today = calendar.startOfDay(for: Date())
        
        // 9:00
        let startTime = calendar.date(bySettingHour: 9, minute: 0, second: 0, of: today)!
        // 17:00
        let endTime = calendar.date(bySettingHour: 17, minute: 0, second: 0, of: today)!
        
        return startTime...endTime
    }
    
    var body: some View {
        Form {
            Section(header: Text("予約時刻(営業時間:9:00〜17:00)")) {
                DatePicker(
                    "時刻",
                    selection: $appointmentTime,
                    in: timeRange,
                    displayedComponents: .hourAndMinute
                )
            }
        }
    }
}

Tip 2: 複数の日付ピッカーを連動させる

開始日と終了日を連動させる実装です。

struct LinkedDatePickers: View {
    @State private var startDate = Date()
    @State private var endDate = Date()
    
    var body: some View {
        Form {
            Section(header: Text("期間設定")) {
                DatePicker(
                    "開始日",
                    selection: $startDate,
                    displayedComponents: .date
                )
                .onChange(of: startDate) { oldValue, newValue in
                    // 開始日が変更されたら、終了日を開始日以降に調整
                    if endDate < newValue {
                        endDate = newValue
                    }
                }
                
                DatePicker(
                    "終了日",
                    selection: $endDate,
                    in: startDate...,
                    displayedComponents: .date
                )
            }
        }
    }
}

Tip 3: 日付選択後の処理を実行

日付が選択されたタイミングで処理を実行する方法です。

struct DatePickerWithAction: View {
    @State private var selectedDate = Date()
    @State private var showConfirmation = false
    
    var body: some View {
        VStack {
            DatePicker("日付", selection: $selectedDate)
                .datePickerStyle(.graphical)
                .onChange(of: selectedDate) { oldValue, newValue in
                    print("日付が変更されました: \(newValue)")
                    showConfirmation = true
                }
                .padding()
            
            if showConfirmation {
                Text("選択されました: \(selectedDate.formatted(date: .long, time: .omitted))")
                    .foregroundColor(.green)
                    .padding()
            }
        }
    }
}

Tip 4: 祝日をハイライト表示

特定の日付を視覚的に強調する方法です(iOS 16+)。

struct HolidayDatePicker: View {
    @State private var selectedDate = Date()
    
    // 祝日のリスト(例)
    let holidays: Set<DateComponents> = [
        DateComponents(month: 1, day: 1),   // 元日
        DateComponents(month: 5, day: 5),   // こどもの日
        DateComponents(month: 12, day: 25)  // クリスマス
    ]
    
    var body: some View {
        DatePicker("日付", selection: $selectedDate)
            .datePickerStyle(.graphical)
            .padding()
    }
}

まとめ

SwiftUIのDatePickerについて、以下のポイントを押さえておきましょう。

DatePickerの基本:

  • 日付・時刻の入力を簡単に実装できる
  • displayedComponentsで表示要素を制御
  • inパラメータで選択範囲を制限
  • 4つのスタイル(automatic, compact, wheel, graphical)から選択可能

主な使い方:

  • 予約システム:日時の選択と範囲制限
  • プロフィール入力:誕生日の入力と年齢計算
  • イベント管理:開始・終了日時の設定
  • スケジュール:時刻の選択

実装のコツ:

  • @Stateでバインディングを忘れずに
  • 日付の範囲制限で入力ミスを防ぐ
  • 適切なスタイルを選んでUXを向上
  • フォーマットを工夫して見やすく表示

スタイルの使い分け:

  • Form内、省スペース → compact
  • 誕生日入力、モーダル → wheel
  • カレンダー重視 → graphical
  • 迷ったら → automatic

DatePickerを使いこなすことで、ユーザーフレンドリーな日付入力UIを簡単に実装できます。

まずはシンプルな日付選択から始めて、徐々に範囲制限や計算機能を追加していきましょう!

参考リンク

プログラミングの独学におすすめ
プログラミング言語の人気オンラインコース
独学でプログラミングを学習している方で、エラーなどが発生して効率よく勉強ができないと悩む方は多いはず。Udemyは、プロの講師が動画で実際のプログラムを動かしながら教えてくれるオンライン講座です。講座の価格は、セール期間中には専門書籍を1冊買うよりも安く済むことが多いです。新しく学びたいプログラミング言語がある方は、ぜひUdemyでオンライン講座を探してみてください。
目次