アプリ開発で避けて通れないのが、日付や時刻の入力機能です。
「予約アプリで日付を選択させたい」 「誕生日の入力欄を作りたい」 「イベントの開始時刻を設定できるようにしたい」
こんな要望、よくありますよね。
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()
}
}
}
コードの解説
- @State private var selectedDate = Date()
- 選択された日付を保持する状態変数
Date()で現在の日時が初期値になる
- selection: $selectedDate
$を付けてバインディング- DatePickerで選択された値が自動的に
selectedDateに反映される
- “日付を選択”
- 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("ホテル予約")
}
}
}
ポイント解説
- 日付の連動
- チェックアウトはチェックインの翌日以降に制限
checkOutRangeで動的に範囲を計算
- 入力検証
- 名前が空の場合はボタンを無効化
.disabled()で実装
- 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("この内容で登録しますか?")
}
}
}
}
ポイント解説
- 年齢制限
- 18歳以上100歳以下に制限
dateRangeで計算
- 年齢の自動計算
Calendar.dateComponentsを使用- リアルタイムで年齢を表示
- 確認機能
- アラートで登録前に確認
- ユーザビリティの向上
実践例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)
}
}
}
ポイント解説
- 終日切り替え
- Toggleで終日イベントに対応
- 終日の場合は時刻選択を非表示
- 日時の連動
- 終了日時は開始日時以降に制限
in: startDate...で実装
- スタイルの使い分け
- 開始日時: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を簡単に実装できます。
まずはシンプルな日付選択から始めて、徐々に範囲制限や計算機能を追加していきましょう!