maui-media-picker について
.NET MAUI アプリにメディア選択や撮影を実装する開発者向けの Media Picker ガイドです。キャンセル、スレッド、ストリーム、プラットフォーム固有の注意点を扱います。
主な機能
- 01キャンセルと空の結果への対応
- 02UI スレッドでのピッカー呼び出し
- 03カメラ撮影対応の確認
- 04読み込みストリームの破棄
- 05プラットフォーム権限と選択数制限への対応
活用シーン
- 01単一または複数写真の選択機能の追加
- 02カメラ対応を確認した上での写真撮影
- 03Android と iOS のメディア権限設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-media-pickerインストール後、Claude Code で/maui-media-pickerを入力して読み込まれたか確認します。
同梱ファイルreferences/media-picker-api.md
詳細
.NET MAUI メディアピッカー — 注意点とベストプラクティス
よくある間違い
1. キャンセルされたかどうかを確認しない
単一選択では null、複数選択では空のリストが返されます。確認しないと NullReferenceException が発生します。
// ❌ Crashes when user cancels
var photo = await MediaPicker.Default.PickPhotoAsync();
using var stream = await photo.OpenReadAsync();
// ✅ Always check for null / empty
var photo = await MediaPicker.Default.PickPhotoAsync();
if (photo is null) return;
using var stream = await photo.OpenReadAsync();
2. バックグラウンドスレッドから呼び出す
すべての MediaPicker メソッドは UI スレッドで実行する必要があります。そうしないと例外が発生します。
// ❌ Will throw on some platforms
await Task.Run(async () => await MediaPicker.Default.PickPhotoAsync());
// ✅ Call directly from a command or event handler on the UI thread
var photo = await MediaPicker.Default.PickPhotoAsync();
3. IsCaptureSupported を確認しない
カメラのないデバイス(エミュレーターや一部のタブレット)で撮影メソッドを呼び出すと、例外が発生します。
// ❌ Crashes on devices without a camera
var photo = await MediaPicker.Default.CapturePhotoAsync();
// ✅ Guard with capability check
if (MediaPicker.Default.IsCaptureSupported)
{
var photo = await MediaPicker.Default.CapturePhotoAsync();
}
4. ストリームを破棄しない
OpenReadAsync() は、破棄する必要があるストリームを返します。
// ❌ Leaks the stream
var stream = await photo.OpenReadAsync();
var bytes = ReadAllBytes(stream);
// ✅ Dispose with using
using var stream = await photo.OpenReadAsync();
プラットフォーム固有の注意点
⚠️ Android/Windows では SelectionLimit が適用されません
MediaPickerOptions.SelectionLimit は参考値です。必ず自分で件数を検証してください。
var options = new MediaPickerOptions { SelectionLimit = 5 };
var results = await MediaPicker.Default.PickPhotosAsync(options);
if (results.Count() > 5)
{
// Warn user or take only the first 5
results = results.Take(5);
}
⚠️ Android のストレージ権限は API 33 で分かれます
- API ≤ 32:
READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGEが必要です - API ≥ 33:
READ_MEDIA_IMAGES/READ_MEDIA_VIDEOが必要です(広範なストレージ権限は無視されます) - Play ストアの警告を避けるため、古い権限には
android:maxSdkVersion="32"を指定してください
⚠️ iOS で完全な機能を使うには plist キーが4つすべて必要です
NSCameraUsageDescription、NSMicrophoneUsageDescription、
NSPhotoLibraryUsageDescription、NSPhotoLibraryAddUsageDescription のいずれかが欠けていると、
その機能にアクセスした際に実行時クラッシュが発生します。
⚠️ Android ではカメラのインテントに <queries> が必要です
AndroidManifest.xml に IMAGE_CAPTURE のクエリがないと、パッケージの可視性制限により、Android 11 以降で CapturePhotoAsync
が何も表示せずに失敗する場合があります。
判断基準
| シナリオ | メソッド |
|---|---|
| ユーザーが写真を1枚選択する | PickPhotoAsync() |
| ユーザーが複数の写真を選択する(.NET 10 以降) | SelectionLimit を指定して PickPhotosAsync() |
| アプリでカメラ撮影が必要 | IsCaptureSupported を確認 → CapturePhotoAsync() |
| 選択したファイルを永続的に保存する | ストリームを FileSystem.AppDataDirectory にコピーする |
| 写真のメタデータが必要 | MediaPickerOptions で PreserveMetaData = true を設定する |
チェックリスト
- すべてのピッカー呼び出しを UI スレッドで実行する
- 選択/撮影の呼び出し後に毎回 null/空の値を確認する
- 撮影メソッドの前に
IsCaptureSupportedでガードする - ストリームを
usingで破棄する - Android マニフェストに
maxSdkVersionを指定した新旧両方のストレージ権限がある - Android マニフェストにカメラインテント用の
<queries>ブロックがある - iOS の
Info.plistに使用目的を説明するキーが4つすべてある - プラットフォームの値をそのまま信頼せず、コード内で
SelectionLimitを検証する