Freigeben über


Auswählen eines Fotos aus der Bildbibliothek

Dieser Artikel führt durch die Erstellung einer Anwendung, die es dem Benutzer ermöglicht, ein Foto aus der Bildbibliothek des Smartphones auszuwählen. Da Xamarin.Forms diese Funktion nicht enthält, ist es erforderlich, mithilfe von DependencyService auf native APIs auf jeder Plattform zuzugreifen.

Erstellen der Schnittstelle

Erstellen Sie zunächst eine Schnittstelle in freigegebenem Code, der die gewünschte Funktionalität ausdrückt. Im Falle einer Anwendung zur Auswahl eines Fotos ist nur eine Methode erforderlich. Dies ist in der IPhotoPickerService Schnittstelle in der .NET Standard-Bibliothek des Beispielcodes definiert:

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

Die GetImageStreamAsync-Methode ist als asynchron definiert, da die Methode schnell zurückgeben muss. Ein Stream-Objekt für das ausgewählte Foto kann jedoch erst zurückgegeben werden, wenn der Benutzer die Bildbibliothek durchsucht und eins ausgewählt hat.

Diese Schnittstelle ist plattformübergreifend mithilfe von plattformspezifischem Code implementiert.

iOS-Implementierung

Bei der iOS-Implementierung der IPhotoPickerService-Schnittstelle wird das UIImagePickerController-Element wie in der Vorgehensweise unter Choose a Photo from the Gallery (Auswählen eines Fotos aus dem Katalog) und Beispielcode beschrieben.

Die iOS-Implementierung ist in der PhotoPickerService-Klasse im iOS-Projekt des Beispielcodes enthalten. Um diese Klasse für den DependencyService-Manager sichtbar zu machen, muss die Klasse mit einem [assembly]-Attribut vom Typ Dependency identifiziert werden, öffentlich sein und explizit die IPhotoPickerService-Schnittstelle implementieren:

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

Die GetImageStreamAsync-Methode erstellt ein UIImagePickerController-Element und initialisiert es, um Bilder aus der Fotobibliothek auszuwählen. Es werden zwei Ereignishandler benötigt: einer für die Auswahl eines Fotos durch den Benutzer und der andere zum Abbrechen der Fotobibliotheksanzeige durch den Benutzer. Dem Benutzer wird dann mithilfe der PresentViewController-Methode die Fotobibliothek angezeigt.

An dieser Stelle muss die GetImageStreamAsync-Methode ein Task<Stream>-Objekt an den Code zurückgeben, der sie aufruft. Dieser Task ist erst abgeschlossen, wenn der Benutzer die Interaktion mit der Fotobibliothek beendet hat und einer der Ereignishandler aufgerufen wird. Für solche Situationen ist die TaskCompletionSource-Klasse unverzichtbar. Die Klasse stellt ein Task-Objekt vom richtigen generischen Typ für die Rückgabe aus der GetImageStreamAsync-Methode zur Verfügung. Später kann der Klasse dann signalisiert werden, wenn der Task abgeschlossen ist.

Der Ereignishandler FinishedPickingMedia wird aufgerufen, wenn der Benutzer ein Bild ausgewählt hat. Der Handler stellt jedoch ein UIImage-Objekt bereit und das Task-Element muss ein Stream-Objekt von .NET zurückgeben. Dies erfolgt in zwei Schritten: Das UIImage Objekt wird zuerst in eine PNG- oder JPEG-Speicherdatei konvertiert, die in einem NSData Objekt gespeichert ist, und anschließend wird das NSData Objekt in ein .NET-Objekt Stream konvertiert. Ein Aufruf der SetResult-Methode des TaskCompletionSource-Objekts schließt den Task durch die Bereitstellung des Stream-Objekts ab:

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

Eine iOS-Anwendung erfordert die Zugriffsberechtigung des Benutzers für die Fotobibliothek des Smartphones. Fügen Sie Folgendes zum dict-Abschnitt der Datei „Info.plist“ hinzu:

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

Android-Implementierung

Bei der Android-Implementierung wird die in der Vorgehensweise unter Select an Image (Auswählen eines Bilds) und dem Beispielcode beschriebene Methode verwendet. Die Methode, die jedoch aufgerufen wird, wenn der Benutzer ein Bild aus der Bildbibliothek ausgewählt hat, ist die Überschreibung von OnActivityResult in einer Klasse, die aus Activity abgeleitet ist. Aus diesem Grund wurde die normale MainActivity-Klasse im Android-Projekt um ein Feld, eine Eigenschaft und eine Überschreibung für die OnActivityResult-Methode ergänzt:

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

Die Überschreibung von OnActivityResult gibt die ausgewählte Bilddatei mit einem Uri-Objekt von Android an. Dieses kann jedoch in ein Stream-Objekt von .NET konvertiert werden, indem die OpenInputStream-Methode des ContentResolver-Objekts aufgerufen wird, die aus der ContentResolver-Eigenschaft der Aktivität abgerufen wurde.

Wie bei der iOS-Implementierung wird auch bei der Android-Implementierung ein TaskCompletionSource-Objekt verwendet, das signalisiert, sobald der Task abgeschlossen ist. Dieses TaskCompletionSource-Objekt ist als öffentliche Eigenschaft in der MainActivity-Klasse definiert. So kann auf die Eigenschaft in der PhotoPickerService-Klasse im Android-Projekt verwiesen werden. Dies ist die Klasse mit der GetImageStreamAsync-Methode:

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

Diese Methode greift auf die MainActivity-Klasse für mehrere Zwecke zu: für die Eigenschaft Instance, für das Feld PickImageId, für die Eigenschaft TaskCompletionSource und für den Aufruf von StartActivityForResult. Diese Methode wird durch die FormsAppCompatActivity-Klasse definiert, wobei es sich um die Basisklasse von MainActivity handelt.

UWP-Implementierung

Im Gegensatz zu den iOS- und Android-Implementierungen wird die TaskCompletionSource-Klasse nicht zur Implementierung der Fotoauswahl für die Universelle Windows-Plattform benötigt. Die PhotoPickerService-Klasse verwendet die FileOpenPicker-Klasse für den Zugriff auf die Fotobibliothek. Da die PickSingleFileAsync-Methode von FileOpenPicker selbst asynchron ist, kann die GetImageStreamAsync-Methode einfach await mit dieser Methode (und anderen asynchronen Methoden) verwenden und ein Stream-Objekt zurückgeben:

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

Implementierung in freigegebenem Code

Nachdem die Schnittstelle nun für jede Plattform implementiert wurde, kann der freigegebene Code in der .NET Standard-Bibliothek darauf zugreifen.

Die Benutzeroberfläche enthält eine Schaltfläche (Button), auf die geklickt werden kann, um ein Foto auszuwählen:

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

Der Clicked-Ereignishandler verwendet die DependencyService-Klasse, um GetImageStreamAsync aufzurufen. Dies führt zu einem Aufruf im Plattformprojekt. Wenn die Methode ein Stream-Objekt zurückgibt, legt der Handler die Source-Eigenschaft des image-Objekts auf die Stream-Daten fest:

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