🍏

Cupertino widgets①

2024/12/30に公開

What Cupertino widgets?

Cupertino widgets

Beautiful and high-fidelity widgets that align with Apple's Human Interface Guidelines for iOS and macOS.


iOSおよびmacOS向けのAppleのヒューマンインターフェースガイドラインに沿った、美しく忠実度の高いウィジェット。

FlutterでiOSのUIが作れるWidgetです。FlutterはGoogleが作ったので、基本はAndroidのデザインです。iOSでもAndroidのデザインになる。ExpoならiOSビルドしたらiPhoneのデザインになるのですが、Flutterだと設定が必要です。

iOSのUIが好きな人はこちらを使ってみては?

// いつものはこれ
import 'package:flutter/material.dart';
// Cupertino widgetsはこれ
import 'package:flutter/cupertino.dart';

CupertinoActionSheet class
を使ってみましょうか。どのような場面でこれは使うのか?

CupertinoActionSheet

CupertinoActionSheetは、iOS風のアクションシートを表示するためのWidgetで、以下のような一般的なユースケースがあります:

  1. 共有機能
CupertinoActionSheet(
  title: const Text('共有'),
  actions: [
    CupertinoActionSheetAction(
      child: const Text('Twitter'),
      onPressed: () => shareToTwitter(),
    ),
    CupertinoActionSheetAction(
      child: const Text('LINE'),
      onPressed: () => shareToLine(),
    ),
    CupertinoActionSheetAction(
      child: const Text('Instagram'),
      onPressed: () => shareToInstagram(),
    ),
  ],
)
  1. 画像選択オプション
CupertinoActionSheet(
  title: const Text('写真を選択'),
  actions: [
    CupertinoActionSheetAction(
      child: const Text('カメラで撮影'),
      onPressed: () => openCamera(),
    ),
    CupertinoActionSheetAction(
      child: const Text('ライブラリから選択'),
      onPressed: () => openPhotoLibrary(),
    ),
  ],
)
  1. 投稿の操作メニュー
CupertinoActionSheet(
  actions: [
    CupertinoActionSheetAction(
      child: const Text('編集'),
      onPressed: () => editPost(),
    ),
    CupertinoActionSheetAction(
      child: const Text('保存'),
      onPressed: () => savePost(),
    ),
    CupertinoActionSheetAction(
      isDestructiveAction: true,
      child: const Text('削除'),
      onPressed: () => deletePost(),
    ),
  ],
)
  1. 設定オプション
CupertinoActionSheet(
  title: const Text('表示設定'),
  actions: [
    CupertinoActionSheetAction(
      child: const Text('ダークモード'),
      onPressed: () => toggleDarkMode(),
    ),
    CupertinoActionSheetAction(
      child: const Text('文字サイズ変更'),
      onPressed: () => changeFontSize(),
    ),
  ],
)
  1. ログアウト確認
CupertinoActionSheet(
  title: const Text('ログアウト'),
  message: const Text('本当にログアウトしますか?'),
  actions: [
    CupertinoActionSheetAction(
      isDestructiveAction: true,
      child: const Text('ログアウト'),
      onPressed: () => logout(),
    ),
  ],
  cancelButton: CupertinoActionSheetAction(
    child: const Text('キャンセル'),
    onPressed: () => Navigator.pop(context),
  ),
)

実際のアプリでの使用例:

  1. Instagram
  • 投稿の共有
  • ストーリーの操作
  • 投稿のオプションメニュー
  1. LINE
  • メッセージの操作
  • 写真/動画の選択
  • グループ設定の変更
  1. 食事配達アプリ
  • 注文のキャンセル
  • 配達オプションの選択
  • レストランの並び替え
  1. 音楽アプリ
  • プレイリストの操作
  • 曲の共有
  • 再生オプション

主な特徴:

  • iOS風のデザイン
  • アニメーションの挙動がiOSネイティブに近い
  • キャンセルボタンが独立して表示
  • 破壊的なアクション(削除など)を赤色で表示可能
  • ボタンのスタイルをカスタマイズ可能

使用時の注意点:

  1. Androidでは異なるデザインガイドラインがある
  2. Material Designを使用する場合はBottomSheetを検討
  3. 操作の重要度に応じて適切な表示順序を考慮
  4. キャンセルオプションは常に用意する

公式のそのまま使っても面白くないのでカメラ使うか写真をアップロードするUIを作ってみました。カメラは使えません。すいません🙇

https://youtube.com/shorts/abdOAXlLGLo

https://pub.dev/packages/image_picker

写真選択のアクションシートの実装例を示します。image_pickerパッケージを使用して、実際にカメラやフォトライブラリにアクセスする例を作成します。

main.dart
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:image_picker/image_picker.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const CupertinoApp(
      theme: CupertinoThemeData(brightness: Brightness.light),
      home: MyWidget(),
    );
  }
}

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

  
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  File? _image;
  final ImagePicker _picker = ImagePicker();

  // カメラで撮影
  Future<void> _openCamera() async {
    Navigator.pop(context); // アクションシートを閉じる
    final XFile? photo = await _picker.pickImage(source: ImageSource.camera);
    if (photo != null) {
      setState(() {
        _image = File(photo.path);
      });
    }
  }

  // フォトライブラリから選択
  Future<void> _openPhotoLibrary() async {
    Navigator.pop(context); // アクションシートを閉じる
    final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
    if (image != null) {
      setState(() {
        _image = File(image.path);
      });
    }
  }

  // アクションシートを表示
  void _showActionSheet(BuildContext context) {
    showCupertinoModalPopup<void>(
      context: context,
      builder: (BuildContext context) => CupertinoActionSheet(
        title: const Text('写真を選択'),
        message: const Text('写真を追加する方法を選択してください'),
        actions: <CupertinoActionSheetAction>[
          CupertinoActionSheetAction(
            onPressed: _openCamera,
            child: const Text('カメラで撮影'),
          ),
          CupertinoActionSheetAction(
            onPressed: _openPhotoLibrary,
            child: const Text('ライブラリから選択'),
          ),
        ],
        cancelButton: CupertinoActionSheetAction(
          isDefaultAction: true,
          onPressed: () => Navigator.pop(context),
          child: const Text('キャンセル'),
        ),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('写真選択サンプル'),
      ),
      child: SafeArea(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 選択した画像を表示
              if (_image != null)
                Container(
                  width: 200,
                  height: 200,
                  margin: const EdgeInsets.only(bottom: 20),
                  decoration: BoxDecoration(
                    image: DecorationImage(
                      image: FileImage(_image!),
                      fit: BoxFit.cover,
                    ),
                    borderRadius: BorderRadius.circular(10),
                  ),
                )
              else
                Container(
                  width: 200,
                  height: 200,
                  margin: const EdgeInsets.only(bottom: 20),
                  decoration: BoxDecoration(
                    color: CupertinoColors.systemGrey5,
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: const Center(
                    child: Text('写真が選択されていません'),
                  ),
                ),
              // 写真選択ボタン
              CupertinoButton.filled(
                onPressed: () => _showActionSheet(context),
                child: const Text('写真を選択'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

このコードを使用するには、以下の手順が必要です:

  1. まずpubspec.yamlimage_pickerを追加:
dependencies:
  flutter:
    sdk: flutter
  image_picker: ^1.1.2  # 最新バージョンを使用
  1. iOSの場合、ios/Runner/Info.plistに権限を追加:
<key>NSCameraUsageDescription</key>
<string>写真を撮影するためにカメラを使用します</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>写真を選択するためにフォトライブラリにアクセスします</string>

<true/>の下に配置する。

主な機能:

  1. カメラで写真を撮影
  2. フォトライブラリから写真を選択
  3. 選択した写真のプレビュー表示
  4. iOS風のUIデザイン

このサンプルは以下のような実際のアプリケーションで応用できます:

  • プロフィール画像の設定
  • 商品写真のアップロード
  • 記事への画像添付
  • フォトアルバムの作成

写真選択後の処理(アップロードなど)を追加することで、実用的な機能として使用できます。

Discussion