Escolhendo uma foto da biblioteca de imagens
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. Por Xamarin.Forms não incluir 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 seguida, o PresentViewController
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 é primeiro convertido 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 OnActivityResult
indica 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;
}