Escolhendo uma foto da biblioteca de imagens

Baixar exemplo Baixar o exemplo

Este artigo descreve a criação de um aplicativo que permite que o usuário escolha uma foto da biblioteca de imagens de seu telefone. Como Xamarin.Forms não inclui essa funcionalidade, é necessário usar DependencyService para acessar APIs nativas em cada plataforma.

Como criar a interface

Primeiro, crie no código compartilhado uma interface que expressa a funcionalidade desejada. No caso de um aplicativo de seleção de fotos, é necessário apenas um método. Isso é definido na IPhotoPickerService interface na biblioteca .NET Standard do código de exemplo:

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

O método GetImageStreamAsync é definido como assíncrono porque ele deve ser retornado rapidamente, mas ele não pode retornar um objeto Stream para a foto selecionada até que o usuário tenha navegado na biblioteca de imagens e selecionado uma.

Essa interface é implementada em todas as plataformas usando código específico da plataforma.

Implementação de iOS

A implementação de iOS da interface IPhotoPickerService usa o UIImagePickerController conforme descrito em Escolher uma foto da galeria e no código de exemplo.

A implementação de iOS está contida na classe PhotoPickerService no projeto do iOS do código de exemplo. Para tornar essa classe visível para o gerenciador do DependencyService, ela deve ser identificada com um atributo [assembly] do tipo Dependency, e a classe deve ser pública e implementar explicitamente a interface 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;
        }
        ...
    }
}

O método GetImageStreamAsync cria um UIImagePickerController e o inicializa para selecionar imagens da biblioteca de fotos. São necessários dois manipuladores de eventos: um para quando o usuário seleciona uma foto e outro para quando o usuário cancela a exibição da biblioteca de fotos. Em PresentViewController seguida, o método exibe a biblioteca de fotos para o usuário.

Neste ponto, o método GetImageStreamAsync deve retornar um objeto Task<Stream> para o código que o está chamando. Essa tarefa é concluída somente quando o usuário termina de interagir com a biblioteca de fotos e um dos manipuladores de eventos é chamado. Para situações como essa, a classe TaskCompletionSource é essencial. A classe fornece um objeto Task do tipo genérico adequado para retornar do método GetImageStreamAsync, e mais tarde a classe poderá ser sinalizada quando a tarefa for concluída.

O manipulador de eventos FinishedPickingMedia é chamado quando o usuário seleciona uma imagem. No entanto, o manipulador fornece um objeto UIImage e o Task deve retornar um objeto Stream do .NET. Isso é feito em duas etapas: o UIImage objeto é convertido primeiro em um arquivo PNG ou JPEG na memória armazenado em um NSData objeto e, em seguida, o NSData objeto é convertido em um objeto .NET Stream . Uma chamada para o método SetResult do objeto TaskCompletionSource conclui a tarefa fornecendo o objeto 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;
        }
    }
}

Um aplicativo iOS requer permissão do usuário para acessar a biblioteca de fotos do telefone. Adicione o seguinte à seção dict do arquivo Info.plist:

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

Implementação de Android

A implementação de Android usa a técnica descrita em Selecionar uma imagem e o código de exemplo. No entanto, o método que é chamado quando o usuário selecionou uma imagem da biblioteca de imagens é uma substituição de OnActivityResult em uma classe que deriva de Activity. Por esse motivo, a classe MainActivity normal no projeto do Android foi complementada com um campo, uma propriedade e uma substituição do método 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);
            }
        }
    }
}

A substituição OnActivityResultindica o arquivo de imagem selecionado com um objeto Uri do Android, mas ele pode ser convertido em um objeto Stream do .NET chamando o método OpenInputStream do objeto ContentResolver que foi obtido da propriedade ContentResolver da atividade.

Assim como a implementação do iOS, a implementação do Android usa um TaskCompletionSource para sinalizar quando a tarefa foi concluída. Esse objeto TaskCompletionSource é definido como uma propriedade pública na classe MainActivity. Isso permite que a propriedade seja referenciada na classe PhotoPickerService no projeto do Android. Essa é a classe com o método 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;
        }
    }
}

Esse método acessa a classe MainActivity para várias finalidades: para a propriedade Instance, para o campo PickImageId, para a propriedade TaskCompletionSource e para chamar StartActivityForResult. O método é definido pela classe FormsAppCompatActivity, que é a classe base de MainActivity.

Implementação da UWP

Diferente das implementações de iOS e Android, a implementação do seletor de fotos na Plataforma Universal do Windows não requer a classe TaskCompletionSource. A classe PhotoPickerService usa a classe FileOpenPicker para ter acesso à biblioteca de fotos. Como o método PickSingleFileAsync de FileOpenPicker é assíncrono, o método GetImageStreamAsync pode simplesmente usar await com esse método (e outros métodos assíncronos) e retornar um objeto 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();
        }
    }
}

Implementação em código compartilhado

Agora que a interface foi implementada para cada plataforma, o código compartilhado na biblioteca do .NET Standard aproveitar seus recursos.

A interface do usuário inclui um Button que pode ser clicado para escolher uma foto:

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

O manipulador de eventos Clicked usa a classe DependencyService para chamar GetImageStreamAsync. Isso resulta em uma chamada para o projeto da plataforma. Se o método retornar um objeto Stream, o manipulador definirá a propriedade Source do objeto image para os dados 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;
}