Sélection d’une photo dans la bibliothèque d’images

Télécharger l’exemple Télécharger l’exemple

Cet article vous montre comment créer une application qui permet à l’utilisateur de sélectionner une photo dans la bibliothèque d’images du téléphone. Étant donné que Xamarin.Forms n’inclut pas cette fonctionnalité, il est nécessaire d’utiliser DependencyService pour accéder aux API natives sur chaque plateforme.

Création de l’interface

Tout d’abord, créez une interface dans le code partagé qui expose la fonctionnalité que vous souhaitez implémenter. Dans le cas d’une application de sélection de photo, une seule méthode est requise. Ceci est défini dans l’interface IPhotoPickerService de la bibliothèque .NET Standard de l’exemple de code :

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

La méthode GetImageStreamAsync est définie comme asynchrone, car elle doit retourner le résultat rapidement, mais elle ne doit pas retourner d’objet Stream pour la photo sélectionnée tant que l’utilisateur n’a pas parcouru la bibliothèque d’images et sélectionné une photo.

Cette interface est implémentée dans chaque plateforme à l’aide de code spécifique.

Implémentation iOS

L’implémentation iOS de l’interface IPhotoPickerService utilise UIImagePickerController de la manière décrite dans la recette Choisir une photo dans la galerie et l’exemple de code.

L’implémentation iOS est contenue dans la classe PhotoPickerService dans le projet iOS de l’exemple de code. Pour que cette classe soit visible par le manager DependencyService, la classe doit être identifiée par un attribut [assembly] de type Dependency, être publique et implémenter de manière explicite l’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;
        }
        ...
    }
}

La méthode GetImageStreamAsync crée un UIImagePickerController et l’initialise pour sélectionner des images dans la photothèque. Deux gestionnaires d’événements sont nécessaires : l’un est utilisé quand l’utilisateur sélectionne une photo et l’autre quand l’utilisateur annule l’affichage de la photothèque. La PresentViewController méthode affiche ensuite la bibliothèque de photos à l’utilisateur.

À ce stade, la méthode GetImageStreamAsync doit retourner un objet Task<Stream> au code qui l’appelle. Cette tâche est exécutée uniquement quand l’utilisateur a fini d’interagir avec la photothèque et que l’un des gestionnaires d’événements est appelé. Dans ces situations, la classe TaskCompletionSource est essentielle. La classe fournit un objet Task du type générique approprié à retourner de la méthode GetImageStreamAsync, et la classe peut être signalée ultérieurement à la fin de la tâche.

Le gestionnaire d’événements FinishedPickingMedia est appelé quand l’utilisateur a sélectionné une image. Toutefois, le gestionnaire fournit un objet UIImage et Task doit retourner un objet Stream .NET. Cela se fait en deux étapes : l’objet UIImage est d’abord converti en fichier PNG ou JPEG en mémoire stocké dans un NSData objet, puis l’objet NSData est converti en objet .NET Stream . Un appel à la méthode SetResult de l’objet TaskCompletionSource exécute la tâche en fournissant l’objet 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;
        }
    }
}

Une application iOS doit être autorisée par l’utilisateur à accéder à la photothèque du téléphone. Ajoutez le code suivant dans la section dict du fichier Info.plist :

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

Implémentation Android

L’implémentation Android utilise la technique décrite dans la recette Sélectionner une image et l’exemple de code. Toutefois, la méthode appelée quand l’utilisateur a sélectionné une image dans la bibliothèque d’images est une substitution de OnActivityResult dans une classe dérivée de Activity. Pour cette raison, la classe MainActivity normale dans le projet Android a été complétée par un champ, une propriété et une substitution de la méthode 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);
            }
        }
    }
}

La substitution de OnActivityResult spécifie le fichier de l’image sélectionnée à l’aide d’un objet Uri Android, mais cet objet peut être converti en objet Stream .NET en appelant la méthode OpenInputStream de l’objet ContentResolver qui a été obtenu à partir de la propriété ContentResolver de l’activité.

Comme l’implémentation iOS, l’implémentation Android utilise un objet TaskCompletionSource pour signaler la fin de la tâche. Cet objet TaskCompletionSource est défini en tant que propriété publique dans la classe MainActivity. Cela permet de référencer la propriété dans la classe PhotoPickerService dans le projet Android. Voici la classe avec la méthode 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;
        }
    }
}

Cette méthode accède à la classe MainActivity à plusieurs fins : pour la propriété Instance, pour le champ PickImageId, pour la propriété TaskCompletionSource et pour appeler StartActivityForResult. Cette méthode est définie par la classe FormsAppCompatActivity, qui est la classe de base de MainActivity.

Implémentation UWP

Contrairement aux implémentations iOS et Android, l’implémentation du sélecteur de photo pour la plateforme Windows universelle n’utilise pas la classe TaskCompletionSource. La classe PhotoPickerService se sert de la classe FileOpenPicker pour accéder à la photothèque. Étant donné que la méthode PickSingleFileAsync de FileOpenPicker est elle-même asynchrone, la méthode GetImageStreamAsync peut simplement utiliser await avec cette méthode (et d’autres méthodes asynchrones) et retourner un objet 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();
        }
    }
}

Implémentation dans le code partagé

Maintenant que l’interface a été implémentée pour chaque plateforme, le code partagé dans la bibliothèque .NET Standard peut l’utiliser.

L’interface utilisateur comprend un Button sur lequel vous pouvez cliquer pour choisir une photo :

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

Le gestionnaire d’événements Clicked utilise la classe DependencyService pour appeler GetImageStreamAsync. Il s’ensuit un appel au projet de plateforme. Si la méthode retourne un objet Stream, le gestionnaire définit la propriété Source de l’objet image aux données 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;
}