画像ライブラリから写真を選択する
この記事では、ユーザーが電話の画像ライブラリから写真を選択できるアプリケーションを作成する手順について説明します。 Xamarin.Forms にはこの機能は含まれていないため、DependencyService
を使用して各プラットフォームのネイティブ API にアクセスする必要があります。
インターフェイスの作成
最初に、目的の機能を表すインターフェイスを共有コード内に作成します。 写真選択アプリケーションの場合、必要なメソッドは 1 つだけです。 これは、サンプル コードの .NET Standard ライブラリの IPhotoPickerService
インターフェイスで定義されています。
namespace DependencyServiceDemos
{
public interface IPhotoPickerService
{
Task<Stream> GetImageStreamAsync();
}
}
GetImageStreamAsync
メソッドは、すぐに戻る必要がありますが、ユーザーが画像ライブラリを参照して写真を選択するまで、選択された写真の Stream
オブジェクトを返すことができないため、非同期として定義されます。
このインターフェイスは、すべてのプラットフォームにおいてプラットフォーム固有のコードを使用して実装されます。
iOS での実装
iOS での IPhotoPickerService
インターフェイスの実装では、「Choose a Photo from the Gallery」(ギャラリーから写真を選択する) レシピとサンプル コードで説明されているように、UIImagePickerController
が使用されます。
iOS での実装は、サンプル コードの iOS プロジェクト内の PhotoPickerService
クラスに含まれます。 このクラスを DependencyService
マネージャーで認識できるようにするには、クラスが Dependency
型の [assembly
] 属性で識別されていて、パブリックとして指定され、IPhotoPickerService
インターフェイスを明示的に実装している必要があります。
[assembly: Dependency (typeof (PhotoPickerService))]
namespace DependencyServiceDemos.iOS
{
public class PhotoPickerService : IPhotoPickerService
{
TaskCompletionSource<Stream> taskCompletionSource;
UIImagePickerController imagePicker;
public Task<Stream> GetImageStreamAsync()
{
// Create and define UIImagePickerController
imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary)
};
// Set event handlers
imagePicker.FinishedPickingMedia += OnImagePickerFinishedPickingMedia;
imagePicker.Canceled += OnImagePickerCancelled;
// Present UIImagePickerController;
UIWindow window = UIApplication.SharedApplication.KeyWindow;
var viewController = window.RootViewController;
viewController.PresentViewController(imagePicker, true, null);
// Return Task object
taskCompletionSource = new TaskCompletionSource<Stream>();
return taskCompletionSource.Task;
}
...
}
}
GetImageStreamAsync
メソッドでは、UIImagePickerController
が作成されて、フォト ライブラリから画像を選択するように初期化されます。 2 つのイベント ハンドラーが必要です。1 つはユーザーが写真を選択するときのためのもので、もう 1 つはユーザーがフォト ライブラリの表示を取り消すときのためのものです。 その後、PresentViewController
メソッドにより、ユーザーに対してフォト ライブラリが表示されます。
この時点で、GetImageStreamAsync
メソッドから呼び出し元のコードに Task<Stream>
オブジェクトを返す必要があります。 このタスクは、ユーザーがフォト ライブラリとの対話を終了し、イベント ハンドラーの 1 つが呼び出されたときにのみ、実行できます。 このような状況に対しては、TaskCompletionSource
クラスが不可欠です。 このクラスでは、GetImageStreamAsync
メソッドから戻すための適切なジェネリック型の Task
オブジェクトが提供されており、後でタスクが完了したときに通知を受け取ることができます。
ユーザーが画像を選択すると、FinishedPickingMedia
イベント ハンドラーが呼び出されます。 ただし、ハンドラーでは UIImage
オブジェクトが提供されますが、Task
からは .NET の Stream
オブジェクトが返される必要があります。 これは 2 つのステップで行われます。最初に UIImage
オブジェクトがメモリ内 PNG または JPEG ファイルに変換されて NSData
オブジェクトに格納され、次に NSData
オブジェクトが .NET の Stream
オブジェクトに変換されます。 TaskCompletionSource
オブジェクトの SetResult
メソッドに対する呼び出しでは、Stream
オブジェクトを提供することによってタスクが完了されます。
namespace DependencyServiceDemos.iOS
{
public class PhotoPickerService : IPhotoPickerService
{
TaskCompletionSource<Stream> taskCompletionSource;
UIImagePickerController imagePicker;
...
void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)
{
UIImage image = args.EditedImage ?? args.OriginalImage;
if (image != null)
{
// Convert UIImage to .NET Stream object
NSData data;
if (args.ReferenceUrl.PathExtension.Equals("PNG") || args.ReferenceUrl.PathExtension.Equals("png"))
{
data = image.AsPNG();
}
else
{
data = image.AsJPEG(1);
}
Stream stream = data.AsStream();
UnregisterEventHandlers();
// Set the Stream as the completion of the Task
taskCompletionSource.SetResult(stream);
}
else
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
}
imagePicker.DismissModalViewController(true);
}
void OnImagePickerCancelled(object sender, EventArgs args)
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
imagePicker.DismissModalViewController(true);
}
void UnregisterEventHandlers()
{
imagePicker.FinishedPickingMedia -= OnImagePickerFinishedPickingMedia;
imagePicker.Canceled -= OnImagePickerCancelled;
}
}
}
iOS アプリケーションが電話のフォト ライブラリにアクセスするには、ユーザーからのアクセス許可が必要です。 Info.plist ファイルの dict
セクションに、以下を追加します。
<key>NSPhotoLibraryUsageDescription</key>
<string>Picture Picker uses photo library</string>
Android での実装
Android での実装では、イメージ選択レシピとサンプル コードで説明されている手法が使用されます。 ただし、ユーザーが画像ライブラリから画像を選択したときに呼び出されるメソッドは、OnActivityResult
の派生クラスでの Activity
のオーバーライドです。 このため、Android プロジェクトの通常の MainActivity
クラスは、フィールド、プロパティ、および OnActivityResult
メソッドのオーバーライドで補完されています。
public class MainActivity : FormsAppCompatActivity
{
internal static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
// ...
Instance = this;
}
// ...
// Field, property, and method for Picture Picker
public static readonly int PickImageId = 1000;
public TaskCompletionSource<Stream> PickImageTaskCompletionSource { set; get; }
protected override void OnActivityResult(int requestCode, Result resultCode, Intent intent)
{
base.OnActivityResult(requestCode, resultCode, intent);
if (requestCode == PickImageId)
{
if ((resultCode == Result.Ok) && (intent != null))
{
Android.Net.Uri uri = intent.Data;
Stream stream = ContentResolver.OpenInputStream(uri);
// Set the Stream as the completion of the Task
PickImageTaskCompletionSource.SetResult(stream);
}
else
{
PickImageTaskCompletionSource.SetResult(null);
}
}
}
}
OnActivityResult
のオーバーライドでは選択された画像ファイルが Android の Uri
オブジェクトで示されていますが、これは、アクティビティの ContentResolver
プロパティから取得された ContentResolver
オブジェクトの OpenInputStream
メソッドを呼び出すことによって、.NET の Stream
オブジェクトに変換できます。
iOS での実装と同様に、Android での実装では TaskCompletionSource
を使用してタスク完了時の通知を受け取ります。 この TaskCompletionSource
オブジェクトは、MainActivity
クラスでパブリック プロパティとして定義されています。 これにより、Android プロジェクトの PhotoPickerService
クラスでこのプロパティを参照できます。 これは、GetImageStreamAsync
メソッドを含むクラスです。
[assembly: Dependency(typeof(PhotoPickerService))]
namespace DependencyServiceDemos.Droid
{
public class PhotoPickerService : IPhotoPickerService
{
public Task<Stream> GetImageStreamAsync()
{
// Define the Intent for getting images
Intent intent = new Intent();
intent.SetType("image/*");
intent.SetAction(Intent.ActionGetContent);
// Start the picture-picker activity (resumes in MainActivity.cs)
MainActivity.Instance.StartActivityForResult(
Intent.CreateChooser(intent, "Select Picture"),
MainActivity.PickImageId);
// Save the TaskCompletionSource object as a MainActivity property
MainActivity.Instance.PickImageTaskCompletionSource = new TaskCompletionSource<Stream>();
// Return Task object
return MainActivity.Instance.PickImageTaskCompletionSource.Task;
}
}
}
このメソッドでは、Instance
プロパティ、PickImageId
フィールド、TaskCompletionSource
プロパティ、StartActivityForResult
の呼び出しなど、複数の目的で MainActivity
クラスがアクセスされます。 このメソッドは、MainActivity
の基底クラスである FormsAppCompatActivity
クラスによって定義されています。
UWP での実装
iOS や Android での実装とは異なり、ユニバーサル Windows プラットフォーム用の写真ピッカーの実装では、TaskCompletionSource
クラスは必要ありません。 PhotoPickerService
クラスでは、FileOpenPicker
クラスを使用してフォト ライブラリへのアクセスが取得されます。 FileOpenPicker
の PickSingleFileAsync
メソッド自体が非同期なので、GetImageStreamAsync
メソッドでは、そのメソッド (および他の非同期メソッド) で await
を単に使用して、Stream
オブジェクトを返すことができます。
[assembly: Dependency(typeof(PhotoPickerService))]
namespace DependencyServiceDemos.UWP
{
public class PhotoPickerService : IPhotoPickerService
{
public async Task<Stream> GetImageStreamAsync()
{
// Create and initialize the FileOpenPicker
FileOpenPicker openPicker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
};
openPicker.FileTypeFilter.Add(".jpg");
openPicker.FileTypeFilter.Add(".jpeg");
openPicker.FileTypeFilter.Add(".png");
// Get a file and return a Stream
StorageFile storageFile = await openPicker.PickSingleFileAsync();
if (storageFile == null)
{
return null;
}
IRandomAccessStreamWithContentType raStream = await storageFile.OpenReadAsync();
return raStream.AsStreamForRead();
}
}
}
共有コードでの実装
各プラットフォーム用のインターフェイスを実装したので、.NET Standard ライブラリの共有コードでそれを利用できます。
UI には、クリックすると写真を選択できる Button
が含まれます。
<Button Text="Pick Photo"
Clicked="OnPickPhotoButtonClicked" />
Clicked
イベント ハンドラーでは DependencyService
クラスが使われ、GetImageStreamAsync
が呼び出されます。 これによって、プラットフォーム プロジェクトが呼び出されます。 メソッドにより Stream
オブジェクトが返された場合、ハンドラーによって image
オブジェクトの Source
プロパティがその Stream
データに設定されます。
async void OnPickPhotoButtonClicked(object sender, EventArgs e)
{
(sender as Button).IsEnabled = false;
Stream stream = await DependencyService.Get<IPhotoPickerService>().GetImageStreamAsync();
if (stream != null)
{
image.Source = ImageSource.FromStream(() => stream);
}
(sender as Button).IsEnabled = true;
}