【Flutter（iOS向け）】ドラムロールUIで生年月日の入力 ~CupertinoDatePicker~

## はじめに
iOSアプリで生年月日を入力する際、最も一般的なUIは**ドラムロール式のピッカー**です。  
Flutterには `CupertinoDatePicker` というiOS標準のUIを再現できるウィジェットがあります。  
本記事では `CupertinoDatePicker` を使って、生年月日入力を実装する方法を解説します。
![Simulator Screen Recording - iPhone 16 - 2025-01-29 at 15.32.53.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535035/039d11a7-b409-2b5a-a952-823eea5d0c0b.gif)

【補足】
違う方法でのドラムロールUIで生年月日の入力の記事も掲載中です！！
ご確認いただけましたら幸いです！
https://qiita.com/_kansae_/items/8ee0725f49d7b1b400ab

## 目次
1. CupertinoDatePickerとは
1. iOSアプリにおける生年月日入力のUI
1. CupertinoDatePickerの実装方法
1. CupertinoDatePickerの実装方法（日本語対応）

## 1. CupertinoDatePickerとは
`CupertinoDatePicker` は、iOSのドラムロール形式の日付選択UIを提供するウィジェットです。  
iOSネイティブの `UIDatePicker` に似ており、iPhoneユーザーに馴染みのあるデザインを実現できます。

[【公式】CupertinoDatePickerについて](https://api.flutter.dev/flutter/cupertino/CupertinoDatePicker-class.html)

## 2. iOSアプリにおける生年月日入力の適正UI
iOSでは、**生年月日はカレンダーよりもドラムロール形式のピッカーが一般的**です。  
この形式のメリットは以下の通りです。

- **スクロール操作で素早く日付選択が可能**
- **年、月、日を個別に選べるため間違えにくい**
- **iOSユーザーが違和感なく操作できる**

※あくまで一般的なので、絶対に「CupertinoDatePicker」を使用してくださいというわけではありません。

## 3. 今回の実装について
今回は**TextFormFieldをタップするとピッカーが開く** 仕様にします。  
この実装では、`showModalBottomSheet` を使い、下からスライド表示されるモーダルで `CupertinoDatePicker` を表示します。
また日本語対応についての実装例も記載しておりますので参考にしていただけたらと思います。

## 4. CupertinoDatePickerの実装方法
以下のコードで、生年月日選択UIを実装できます。

### **実装コード**
```dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false, // デバッグバナーを非表示に
      home: DatePickerExample(), // メイン画面をDatePickerExampleに設定
    );
  }
}

class DatePickerExample extends StatefulWidget {
  const DatePickerExample({super.key});

  @override
  _DatePickerExampleState createState() => _DatePickerExampleState();
}

class _DatePickerExampleState extends State<DatePickerExample> {
  // 生年月日を管理するためのTextEditingController
  final TextEditingController _dateController = TextEditingController();

  // 日付選択用のモーダルボトムシートを表示
  void _showDatePicker(BuildContext context) {
    showModalBottomSheet(
      context: context,
      builder: (BuildContext builder) {
        return SizedBox(
          height: 250, // ボトムシートの高さを設定
          child: Column(
            children: [
              // CupertinoDatePicker（iOS風の日付ピッカー）
              Expanded(
                child: CupertinoDatePicker(
                  mode: CupertinoDatePickerMode.date, // 日付モードに設定
                  initialDateTime: DateTime(2000, 1, 1), // 初期日付
                  minimumDate: DateTime(1950, 1, 1), // 最小日付（1950年1月1日）
                  maximumDate: DateTime.now(), // 最大日付（今日の日付）
                  onDateTimeChanged: (DateTime newDate) {
                    // 日付変更時にテキストフィールドに反映
                    setState(() {
                      _dateController.text =
                      "${newDate.year}/${newDate.month}/${newDate.day}";
                    });
                  },
                ),
              ),
              // 完了ボタン
              TextButton(
                onPressed: () => Navigator.pop(context), // ボトムシートを閉じる
                child: Text("完了"),
              )
            ],
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('生年月日入力')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: TextFormField(
          controller: _dateController,
          readOnly: true, // タップでのみ日付選択可能
          decoration: InputDecoration(
            labelText: "生年月日",
            fillColor: Colors.white,
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(8),
              borderSide: BorderSide(color: Colors.blue, width: 1.5),
            ),
          ),
          // フィールドがタップされた時に日付ピッカーを表示
          onTap: () => _showDatePicker(context),
        ),
      ),
    );
  }
}
```

## 5. 日本語表記にする方法

### pubspec.yamlにflutter_localizationsを追記

・追記後 `Pub get`の実行
```yaml
  flutter_localizations:
    sdk: flutter
```
・４のコードにflutter_localizationsで日本語表記にするコードを追記

### **実装コード**
```dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
//日本語表記にするためにflutter_localizationsのインポート
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false, // デバッグバナーを非表示に
      home: DatePickerExample(), // メイン画面をDatePickerExampleに設定
      //日本語表記のためのローカライズ設定
      localizationsDelegates: [
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('ja'),
      ],
    );
  }
}

class DatePickerExample extends StatefulWidget {
  const DatePickerExample({super.key});

  @override
  _DatePickerExampleState createState() => _DatePickerExampleState();
}

class _DatePickerExampleState extends State<DatePickerExample> {
  // 生年月日を管理するためのTextEditingController
  final TextEditingController _dateController = TextEditingController();

  // 日付選択用のモーダルボトムシートを表示
  void _showDatePicker(BuildContext context) {
    showModalBottomSheet(
      context: context,
      builder: (BuildContext builder) {
        return SizedBox(
          height: 250, // ボトムシートの高さを設定
          child: Column(
            children: [
              // CupertinoDatePicker（iOS風の日付ピッカー）
              Expanded(
                child: CupertinoDatePicker(
                  mode: CupertinoDatePickerMode.date, // 日付モードに設定
                  initialDateTime: DateTime(2000, 1, 1), // 初期日付
                  minimumDate: DateTime(1950, 1, 1), // 最小日付（1950年1月1日）
                  maximumDate: DateTime.now(), // 最大日付（今日の日付）
                  onDateTimeChanged: (DateTime newDate) {
                    // 日付変更時にテキストフィールドに反映
                    setState(() {
                      _dateController.text =
                      "${newDate.year}/${newDate.month}/${newDate.day}";
                    });
                  },
                ),
              ),
              // 完了ボタン
              TextButton(
                onPressed: () => Navigator.pop(context), // ボトムシートを閉じる
                child: Text("完了"),
              )
            ],
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('生年月日入力')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: TextFormField(
          controller: _dateController,
          readOnly: true, // タップでのみ日付選択可能
          decoration: InputDecoration(
            labelText: "生年月日",
            fillColor: Colors.white,
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(8),
              borderSide: BorderSide(color: Colors.blue, width: 1.5),
            ),
          ),
          // フィールドがタップされた時に日付ピッカーを表示
          onTap: () => _showDatePicker(context),
        ),
      ),
    );
  }
}
```

## まとめ
Flutterを使用するようになりAndroidでも作成が可能という点から
iOSではどのようなUIが適切なのかより考えさせられるようになりました。
今回の生年月日入力についても、使用するターゲットや使用用途によっては
カレンダーUIの方が良かったりするのかもしれません。
絶対にこれだ！！はないと思うので臨機応変に対応できるよう
実装の知見を増やしていきたいと思う今日この頃でした👀

誤った内容がありましたらコメントお願い致します！