Выбор фотографии в библиотеке рисунковPicking a Photo from the Picture Library

Скачать пример Скачать примерDownload Sample Download the sample

Эта статья описывает создание приложения, позволяющего пользователю выбрать фотографию в библиотеке рисунков на телефоне.This article walks through the creation of an application that allows the user to pick a photo from the phone's picture library. Так как Xamarin.Forms не содержит эту функцию, требуется использовать DependencyService для доступа к собственным API на каждой платформе.Because Xamarin.Forms does not include this functionality, it is necessary to use DependencyService to access native APIs on each platform.

Создание интерфейсаCreating the interface

Сначала создайте в общем коде интерфейс для реализации нужной функциональности.First, create an interface in shared code that expresses the desired functionality. В случае с приложением для выбора фотографий требуется всего один метод.In the case of a photo-picking application, just one method is required. В примере кода он определяется в интерфейсе IPhotoPickerService в библиотеке .NET Standard:This is defined in the IPhotoPickerService interface in the .NET Standard library of the sample code:

namespace DependencyServiceDemos
{
    public interface IPhotoPickerService
    {
        Task<Stream> GetImageStreamAsync();
    }
}

Метод GetImageStreamAsync определен как асинхронный, так как должен быстро возвращать данные, но он не может возвратить объект Stream для выбранной фотографии, пока пользователь не просмотрит библиотеку рисунков и не выберет один из них.The GetImageStreamAsync method is defined as asynchronous because the method must return quickly, but it can't return a Stream object for the selected photo until the user has browsed the picture library and selected one.

Этот интерфейс реализуется на всех платформах с помощью кода, учитывающего особенности конкретной платформы.This interface is implemented in all the platforms using platform-specific code.

Реализация в iOSiOS implementation

Реализация интерфейса IPhotoPickerService в iOS использует UIImagePickerController, как описано в разделе Выбор фотографии из коллекции и примере кода.The iOS implementation of the IPhotoPickerService interface uses the UIImagePickerController as described in the Choose a Photo from the Gallery recipe and sample code.

Реализация для iOS содержится в классе PhotoPickerService в проекте iOS примера кода.The iOS implementation is contained in the PhotoPickerService class in the iOS project of the sample code. Чтобы сделать этот класс видимым для диспетчераDependencyService, нужно определить класс с атрибутом [assembly] типа Dependency, при этом он должен быть открытым и явно реализовывать интерфейс IPhotoPickerService:To make this class visible to the DependencyService manager, the class must be identified with an [assembly] attribute of type Dependency, and the class must be public and explicitly implement the IPhotoPickerService interface:

[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.PresentModalViewController(imagePicker, true);

            // Return Task object
            taskCompletionSource = new TaskCompletionSource<Stream>();
            return taskCompletionSource.Task;
        }
        ...
    }
}

Метод GetImageStreamAsync создает UIImagePickerController и инициализирует его, чтобы выбрать изображения из библиотеки фотографий.The GetImageStreamAsync method creates a UIImagePickerController and initializes it to select images from the photo library. Требуется два обработчика событий: один, когда пользователь выбирает фотографию, и другой — когда пользователь отменяет отображение библиотеки фотографий.Two event handlers are required: One for when the user selects a photo and the other for when the user cancels the display of the photo library. После этого PresentModalViewController отображает библиотеку фотографий для пользователя.The PresentModalViewController then displays the photo library to the user.

На этом этапе метод GetImageStreamAsync должен возвращать объект Task<Stream> вызывающему его коду.At this point, the GetImageStreamAsync method must return a Task<Stream> object to the code that's calling it. Эта задача выполняется только после того, как пользователь закончил взаимодействие с библиотекой фотографий и вызван один из обработчиков событий.This task is completed only when the user has finished interacting with the photo library and one of the event handlers is called. Для таких ситуаций крайне важен класс TaskCompletionSource.For situations like this, the TaskCompletionSource class is essential. Он предоставляет объект Task соответствующего универсального типа для возврата из метода GetImageStreamAsync, а позднее этому классу можно подать сигнал о выполнении задачи.The class provides a Task object of the proper generic type to return from the GetImageStreamAsync method, and the class can later be signaled when the task is completed.

Обработчик событий FinishedPickingMedia вызывается, когда пользователь выбирает изображение.The FinishedPickingMedia event handler is called when the user has selected a picture. Однако обработчик предоставляет объект UIImage и Task должен возвратить объект .NET Stream.However, the handler provides a UIImage object and the Task must return a .NET Stream object. Это осуществляется в два этапа: сначала объект UIImage преобразуется в JPEG-файл в памяти (хранится в объекте NSData), а затем объект NSData преобразуется в объект .NET Stream.This is done in two steps: The UIImage object is first converted to a JPEG file in memory stored in an NSData object, and then the NSData object is converted to a .NET Stream object. Вызов метода SetResult объекта TaskCompletionSource завершает задачу, предоставляя объект Stream:A call to the SetResult method of the TaskCompletionSource object completes the task by providing the Stream object:

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 = 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 требуется разрешение пользователя для обращения к библиотеке фотографий на телефоне.An iOS application requires permission from the user to access the phone's photo library. Добавьте следующий код в раздел dict файла Info.plist:Add the following to the dict section of the Info.plist file:

<key>NSPhotoLibraryUsageDescription</key>
<string>Picture Picker uses photo library</string>

Реализация в AndroidAndroid implementation

Реализация для Android использует методики, описанные в разделе Выбор изображения и примере кода.The Android implementation uses the technique described in the Select an Image recipe and the sample code. Однако метод, вызываемый при выборе пользователем изображения из библиотеки рисунков, представляет собой переопределение OnActivityResult в классе, производном от Activity.However, the method that is called when the user has selected an image from the picture library is an OnActivityResult override in a class that derives from Activity. Поэтому нормальный класс MainActivity в проекте Android был дополнен полем, свойством и переопределением метода OnActivityResult:For this reason, the normal MainActivity class in the Android project has been supplemented with a field, a property, and an override of the OnActivityResult method:

public class MainActivity : FormsAppCompatActivity
{
    ...
    // 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, но его можно преобразовать в объект .NET Stream, вызвав метод OpenInputStream объекта ContentResolver, который был получен из свойства ContentResolver действия.The OnActivityResultoverride indicates the selected picture file with an Android Uri object, but this can be converted into a .NET Stream object by calling the OpenInputStream method of the ContentResolver object that was obtained from the activity's ContentResolver property.

Как и реализация для iOS, реализация для Android использует TaskCompletionSource, чтобы сигнализировать о завершении задачи.Like the iOS implementation, the Android implementation uses a TaskCompletionSource to signal when the task has been completed. Этот объект TaskCompletionSource определен в виде открытого свойства в классе MainActivity.This TaskCompletionSource object is defined as a public property in the MainActivity class. Это позволяет ссылаться на свойство в классе PhotoPickerService проекта Android.This allows the property to be referenced in the PhotoPickerService class in the Android project. Это класс с методом GetImageStreamAsync:This is the class with the GetImageStreamAsync method:

[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;
        }
    }
}

Этот метод обращается к классу MainActivity по нескольким причинам: за свойством Instance, за полем PickImageId, за свойством TaskCompletionSource и для вызова StartActivityForResult.This method accesses the MainActivity class for several purposes: for the Instance property, for the PickImageId field, for the TaskCompletionSource property, and to call StartActivityForResult. Этот метод определен классом FormsAppCompatActivity, который является базовым классом для MainActivity.This method is defined by the FormsAppCompatActivity class, which is the base class of MainActivity.

Реализация на универсальной платформе WindowsUWP implementation

В отличие от реализаций для iOS и Android, реализации средства выбора фотографий для универсальной платформы Windows не требуется класс TaskCompletionSource.Unlike the iOS and Android implementations, the implementation of the photo picker for the Universal Windows Platform does not require the TaskCompletionSource class. Класс PhotoPickerService использует класс FileOpenPicker для получения доступа к библиотеке фотографий.The PhotoPickerService class uses the FileOpenPicker class to get access to the photo library. Так как метод PickSingleFileAsync объекта FileOpenPicker сам является асинхронным, метод GetImageStreamAsync может просто использовать await с этим методом (и другими асинхронными методами) и возвращать объект Stream:Because the PickSingleFileAsync method of FileOpenPicker is itself asynchronous, the GetImageStreamAsync method can simply use await with that method (and other asynchronous methods) and return a Stream object:

[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();
        }
    }
}

Реализация в общем кодеImplementing in shared code

Теперь, когда интерфейс реализован для каждой платформы, общий код в библиотеке .NET Standard может им воспользоваться.Now that the interface has been implemented for each platform, the shared code in the .NET Standard library can take advantage of it.

Пользовательский интерфейс содержит элемент Button, который можно щелкнуть, чтобы выбрать фотографию:The UI includes a Button that can be clicked to choose a photo:

<Button Text="Pick Photo"
        Clicked="OnPickPhotoButtonClicked" />

Обработчик событий Clicked использует класс DependencyService для вызова GetImageStreamAsync.The Clicked event handler uses the DependencyService class to call GetImageStreamAsync. После этого выполняется вызов к проекту платформы.This results in a call to the platform project. Если метод возвращает объект Stream, обработчик задает свойство Source объекта image данным Stream:If the method returns a Stream object, then the handler sets the Source property of the image object to the Stream data:

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;
}