【SwiftUI】DatePickerのカレンダーをボタンで表示させる方法

## はじめに
DatePickerのカレンダーを表示できるボタンを作りたく、実装方法を調べてみました。

## 環境
Xcode 14.1

## 内容
https://developer.apple.com/documentation/swiftui/datepicker

```swift
struct DatePicker<Label> where Label : View
```

## [`DatePicker`](https://developer.apple.com/documentation/swiftui/datepicker)の使い方

`DatePicker`を使うことでカレンダーから日付を選択できる

```swift
@State private var date = Date()

var body: some View {
    DatePicker(
        "Start Date",
        selection: $date,
        displayedComponents: [.date]
    )
}
```

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/9ffd5227-955c-2645-bbe3-0d467267c63f.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/32cc786f-b923-3552-93fb-1ccf5be0dd17.png">

カレンダーから選択可能な期間を指定する場合は、`ClosedRange<Date>`を使う

```swift
@State private var date = Date()
let dateRange: ClosedRange<Date> = {
    let calendar = Calendar.current
    let startComponents = DateComponents(year: 2021, month: 1, day: 1)
    let endComponents = DateComponents(year: 2021, month: 12, day: 31, hour: 23, minute: 59, second: 59)
    return calendar.date(from:startComponents)!
        ...
        calendar.date(from:endComponents)!
}()

var body: some View {
    DatePicker(
        "Start Date",
         selection: $date,
         in: dateRange,
         displayedComponents: [.date, .hourAndMinute]
    )
}
```

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/cdf499c1-0671-c937-8a06-e5417cf3e9fa.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/e20a5f9d-155f-dfa7-dedc-acd4928fe88b.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/0cf2cb94-0863-987c-f301-1550eda9c793.png">

`datePickerStyle(_:)`を使うことで、表示方法を変えられる

```swift
@State private var date = Date()

var body: some View {
    DatePicker(
        "Start Date",
        selection: $date,
        displayedComponents: [.date, .hourAndMinute]
    )
    .datePickerStyle(.compact)
}
```

|`.compact`|`.wheel`|`.graphical`|
|--|--|--|
|<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/83649d66-0c14-306f-59d1-6e1558b61c37.png">|<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/c5dcad9b-e8ef-8a4d-be7c-7d28c472dc87.png">|<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/8c5028af-a5cd-fe19-b231-114a1502426d.png">|


## カレンダーの表示ができるButton①

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/d5f8ad5b-2260-7de7-134c-2662c6ad582a.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/4648d5b4-6ae5-6b49-b40c-f7458bb95da2.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/08db5d9c-62da-0462-a5d5-243ec86af43c.png">

##### 実装

```swift
struct CustomCalendarButtonView: View {
    @State var showDatePicker: Bool = false
    @State var savedDate: Date? = nil
    
    var body: some View {
        ZStack {
            HStack {
                Button {
                    showDatePicker.toggle()
                } label: {
                    Text("カレンダーを表示")
                    Image(systemName: "calendar")
                }
                .buttonStyle(.bordered)
            }
            if showDatePicker {
                CustomDatePicker(
                    showDatePicker: $showDatePicker,
                    savedDate: $savedDate,
                    selectedDate: savedDate ?? Date()
                )
                .animation(.linear, value: savedDate)
                .transition(.opacity)
            }
        }
        
    }
}

struct CustomDatePicker: View {
    @Binding var showDatePicker: Bool
    @Binding var savedDate: Date?
    @State var selectedDate: Date = Date()
    
    var body: some View {
        ZStack {
            Color.black.opacity(0.3)
                .edgesIgnoringSafeArea(.all)
                .onTapGesture {
                    showDatePicker = false
                }
            VStack {
                DatePicker(
                    "",
                    selection: $selectedDate,
                    displayedComponents: [.date, .hourAndMinute]
                )
                .datePickerStyle(.graphical)
                Divider()
                HStack {
                    Button("キャンセル") {
                        showDatePicker = false
                    }
                    Spacer()
                    Button("保存") {
                        savedDate = selectedDate
                        showDatePicker = false
                    }
                }
                .padding(.vertical, 15)
                .padding(.horizontal, 10)
            }
            .padding(.horizontal, 20)
            .background(
                Color.white
                    .cornerRadius(30)
            )
            .padding(.horizontal, 20)
        }
    }
}
```

## カレンダーの表示ができるButton②

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/5c3100b1-0543-9304-6348-b736f4d2978b.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/e1dbcd1d-b443-eb10-f2cd-53e33db12d8c.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/d69e1d8f-cf3c-d2fb-cddd-00b1ef26eee4.png">

##### 実装

```swift
    @State var showDatePicker = false
    @State var date = Date()
    
    var body: some View {
        VStack {
            Button {
                showDatePicker.toggle()
            } label: {
                Text("カレンダーを表示")
                Image(systemName: "calendar")
            }
            .buttonStyle(.bordered)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .overlay {
            ZStack{
                if showDatePicker {
                    Color.black.opacity(0.3)
                        .edgesIgnoringSafeArea(.all)
                        .onTapGesture {
                            showDatePicker = false
                        }
                    DatePicker(
                        "",
                        selection: $date,
                        displayedComponents: [.date, .hourAndMinute]
                    )
                    .datePickerStyle(.graphical)
                    .padding()
                    .background(.white, in: RoundedRectangle(
                        cornerRadius: 10,
                        style: .continuous)
                    )
                    .padding()
                }
            }
            .animation(.default, value: showDatePicker)
        }
    }
```

## 複数選択のできるカレンダーの表示

` MultiDatePicker`を使うことで複数日が選択できるカレンダーが表示できる

```swift
struct MultiDatePicker<Label> where Label : View
```

https://developer.apple.com/documentation/swiftui/multidatepicker

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/8d4edd1d-221b-f9a9-1f67-a38e685f2c3a.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/9db9e55e-5ada-4c7a-8b96-da8e09de46f1.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/467b1b20-8d4f-4c38-aee3-fe012600a7ef.png">

##### 実装

```swift
    @State var showDatePicker = false
    @State private var dates: Set<DateComponents> = []
    
    var body: some View {
        VStack {
            Button {
                showDatePicker.toggle()
            } label: {
                Text("カレンダーを表示")
                Image(systemName: "calendar")
            }
            .buttonStyle(.bordered)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .overlay {
            ZStack{
                if showDatePicker {
                    Color.black.opacity(0.3)
                        .edgesIgnoringSafeArea(.all)
                        .onTapGesture {
                            showDatePicker = false
                        }
                    MultiDatePicker(selection: $dates) {
                        Text("")
                    }
                    .datePickerStyle(.graphical)
                    .padding()
                    .background(.white, in: RoundedRectangle(
                        cornerRadius: 10,
                        style: .continuous)
                    )
                    .padding()
                }
            }
            .animation(.default, value: showDatePicker)
        }
    }
```

# おわりに
参考記事に助けられながら、実装したいものができました！
それと、`MultiDatePicker`がiOS16から使えるようになったようですね


# 参考
https://developer.apple.com/documentation/swiftui/datepicker

https://stackoverflow.com/questions/65532940/date-picker-button-swiftui

http://cloudcafe.tech/?p=109