web-dev-qa-db-fra.com

Accès à la caméra avec Xamarin.Forms

Quelqu'un peut-il donner un court exemple autonome sur la façon d'accéder à la caméra avec Xamarin.Forms 1.3.x? Le simple fait d'appeler l'application de caméra native et de récupérer l'image résultante serait formidable. Afficher une vue en direct sur la page Xamarin.Forms serait génial!

J'ai déjà essayé d'utiliser Xamarin.Mobile et Xamarin.Forms.Labs, mais je n'ai trouvé aucune solution pour travailler sur les deux plates-formes (en se concentrant sur Android et iOS pour l'instant). La plupart des extraits de code trouvés sur le Web (y compris stackoverflow) sont incomplets, par exemple ne montrant pas la mise en œuvre d'un objet IMediaPicker ou où ancrer la méthode pour prendre des photos.

30
Falko

J'ai finalement créé une solution minimale pour iOS et Android.

Le projet partagé

Examinons d'abord le code partagé. Pour une interaction facile entre la classe partagée App et le code spécifique à la plate-forme, nous stockons un Instance statique dans le public static App:

public static App Instance;

De plus, nous afficherons un Image, qui sera rempli de contenu plus tard. Nous créons donc un membre:

readonly Image image = new Image();

Dans le constructeur App, nous stockons le Instance et créons le contenu de la page, qui est un simple button et le image susmentionné:

public App()
{
   Instance = this;

   var button = new Button {
       Text = "Snap!",
       Command = new Command(o => ShouldTakePicture()),
   };

   MainPage = new ContentPage {
       Content = new StackLayout {
       VerticalOptions = LayoutOptions.Center,
           Children = {
                    button,
                    image,
           },
       },
   };
}

Le gestionnaire de clic du bouton appelle l'événement ShouldTakePicture. Il s'agit d'un membre public et les parties de code spécifiques à la plate-forme lui seront attribuées ultérieurement.

public event Action ShouldTakePicture = () => {};

Enfin, nous proposons une méthode publique pour afficher l'image capturée:

public void ShowImage(string filepath)
{
    image.Source = ImageSource.FromFile(filepath);
}

Le projet Android

Sur Android nous modifions le MainActivity. D'abord, nous définissons un chemin pour le fichier image capturé:

static readonly File file = new File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures), "tmp.jpg");

À la fin de OnCreate, nous pouvons utiliser le statique Instance du App créé et affecter un gestionnaire d'événements anonyme, qui lancera un nouveau Intent pour la capture une image:

App.Instance.ShouldTakePicture += () => {
   var intent = new Intent(MediaStore.ActionImageCapture);
   intent.PutExtra(MediaStore.ExtraOutput, Uri.FromFile(file));
   StartActivityForResult(intent, 0);
};

Enfin et surtout, notre activité doit réagir sur l'image résultante. Il poussera simplement son chemin de fichier vers la méthode partagée ShowImage.

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
   base.OnActivityResult(requestCode, resultCode, data);
   App.Instance.ShowImage(file.Path);
}

C'est à peu près ça! N'oubliez pas de définir la "Camera" et la permission "WriteExternalStorage" dans "AndroidManifest.xml"!

Le projet iOS

Pour l'implémentation iOS, nous créons un rendu personnalisé. Par conséquent, nous ajoutons un nouveau fichier "CustomContentPageRenderer" et ajoutons l'attribut Assembly correspondant juste après les instructions using:

[Assembly:ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))]

CustomContentPageRenderer hérite de PageRenderer:

public class CustomContentPageRenderer: PageRenderer
{
    ...
}

Nous remplaçons la méthode ViewDidAppear et ajoutons les parties suivantes.

Créez un nouveau contrôleur de sélecteur d'images se référant à la caméra:

var imagePicker = new UIImagePickerController { SourceType = UIImagePickerControllerSourceType.Camera };

Présentez le contrôleur du sélecteur d'images, dès que l'événement ShouldTakePicture est déclenché:

App.Instance.ShouldTakePicture += () => PresentViewController(imagePicker, true, null);

Après avoir pris la photo, enregistrez-la dans le dossier MyDocuments et appelez la méthode partagée ShowImage:

imagePicker.FinishedPickingMedia += (sender, e) => {
            var filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "tmp.png");
var image = (UIImage)e.Info.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage"));
            InvokeOnMainThread(() => {
                image.AsPNG().Save(filepath, false);
                App.Instance.ShowImage(filepath);
            });
            DismissViewController(true, null);
        };

Et enfin, nous devons gérer une annulation du processus de prise d'image:

imagePicker.Canceled += (sender, e) => DismissViewController(true, null);
34
Falko

Essayez le James Montemagno MediaPlugin .

Vous pouvez installer le plugin à l'aide de Package Manager Console en tapant simplement et en exécutant Install-Package Xam.Plugin.Media -Version 2.6.2 ou bien allez dans Gérer les packages NuGet ... et tapez Xam.Plugin.Media et installez le plugin. (Les plugins doivent être installés dans tous vos projets - y compris les projets clients)

Un readme.txt sera invité et suivez les instructions. Après cela, ajoutez les codes suivants (selon les besoins) à votre projet partagé. Les instructions à suivre dans le fichier readme.txt ci-dessus sont les suivantes.

Pour Android Project

Dans votre BaseActivity ou MainActivity (pour Xamarin.Forms), ajoutez ce code:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
    PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}  

Vous devez également ajouter quelques fichiers de configuration supplémentaires pour adhérer au nouveau mode strict:

  1. Ajoutez ce qui suit à votre AndroidManifest.xml dans les balises de l'application <>:

    <provider Android:name="Android.support.v4.content.FileProvider" 
              Android:authorities="YOUR_APP_PACKAGE_NAME.fileprovider" 
              Android:exported="false" 
              Android:grantUriPermissions="true">
        <meta-data Android:name="Android.support.FILE_PROVIDER_PATHS" 
                   Android:resource="@xml/file_paths"></meta-data>
    </provider>
    

    YOUR_APP_PACKAGE_NAME doit être défini sur le nom de votre package d'application!

  2. Ajoutez un nouveau dossier appelé xml dans votre dossier Resources et ajoutez un nouveau fichier XML appelé file_paths.xml

    Ajoutez le code suivant:

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
        <external-path name="my_images" path="Android/data/YOUR_APP_PACKAGE_NAME/files/Pictures" />
        <external-path name="my_movies" path="Android/data/YOUR_APP_PACKAGE_NAME/files/Movies" />
    </paths>
    

    YOUR_APP_PACKAGE_NAME doit être défini sur le nom de votre package d'application!

Pour iOS Project

Votre application doit disposer de clés dans votre Info.plist pour NSCameraUsageDescription et NSPhotoLibraryUsageDescription pour accéder à l'appareil photo et à la bibliothèque de photos/vidéos de l'appareil. Si vous utilisez les capacités vidéo de la bibliothèque, vous devez également ajouter NSMicrophoneUsageDescription. La chaîne que vous fournissez pour chacune de ces clés sera affichée à l'utilisateur lorsqu'il sera invité à autoriser l'accès à ces fonctionnalités de l'appareil.

Tel que:

<key>NSCameraUsageDescription</key>
<string>This app needs access to the camera to take photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone.</string>

Pour projet partagé

Pour ouvrir simplement l'appareil photo, enregistrer la photo et afficher une alerte avec le chemin du fichier, entrez ce qui suit dans le projet partagé.

if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
    await DisplayAlert("No Camera", ":( No camera avaialble.", "OK");
    return;
}

var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
    PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
    Directory = "Sample",
    Name = "test.jpg"
});

if (file == null)
    return;

await DisplayAlert("File Location", file.Path, "OK"); 
5
Curiousity

Voici ce dont j'avais besoin pour exécuter la capture de caméra asynchrone dans mon application:

Sous iOS:

public async Task<string> TakePicture()
{
    if (await AuthorizeCameraUse())
    {
        var imagePicker = new UIImagePickerController { SourceType = UIImagePickerControllerSourceType.Camera };

        TaskCompletionSource<string> FinishedCamera = new TaskCompletionSource<string>();

        // When user has taken picture
        imagePicker.FinishedPickingMedia += (sender, e) => {
            // Save the file
            var filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "tmp.png");
            var image = (UIImage)e.Info.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage"));
            image.AsPNG().Save(filepath, false);

            // Close the window
            UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);

            // Stop awaiting
            FinishedCamera.SetResult(filepath);
        };

        // When user clicks cancel 
        imagePicker.Canceled += (sender, e) =>
        {
            UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
            FinishedCamera.TrySetCanceled();
        };

        // Show the camera-capture window
        UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(imagePicker, true, null);

        // Now await for the task to complete or be cancelled
        try
        {
            // Return the path we've saved the image in
            return await FinishedCamera.Task;
        }
        catch (TaskCanceledException)
        {
            // handle if the user clicks cancel
        }
    }

    return null;
}

Et si vous avez besoin de la procédure d'autorisation, assurez-vous de renseigner également l'utilisation de la caméra dans info.plist aussi, et voici la fonction pour obtenir l'autorisation:

public static async Task<bool> AuthorizeCameraUse()
{
    var authorizationStatus = AVCaptureDevice.GetAuthorizationStatus(AVMediaType.Video);

    if (authorizationStatus != AVAuthorizationStatus.Authorized)
    {
        return await AVCaptureDevice.RequestAccessForMediaTypeAsync(AVMediaType.Video);
    }
    else
        return true;
}

Sous Android:

private TaskCompletionSource<bool> _tcs_NativeCamera;

public async Task<string> TakePicture()
{
    _tcs_NativeCamera = new TaskCompletionSource<bool>();

    // Launch the camera activity
    var intent = new Intent(MediaStore.ActionImageCapture);
    intent.PutExtra(MediaStore.ExtraOutput, Android.Net.Uri.FromFile(cameraCaptureFilePath));

    NextCaptureType = stype;

    StartActivityForResult(intent, SCAN_NATIVE_CAMERA_CAPTURE_ASYNC);

    // Wait here for the activity return (through OnActivityResult)
    var Result = await _tcs_NativeCamera.Task;

    // Return the camera capture file path
    return Result != Result.Canceled ? cameraCaptureFilePath : null;
}

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);

    switch (requestCode)
    {
        case SCAN_NATIVE_CAMERA_CAPTURE_ASYNC:
            _tcs_NativeCamera.SetResult(resultCode);
            break;
    }
}
1
noelicus

Voici comment vous pouvez le faire sur Xamarin Forms cross Xamarin iOS.

C'est une bonne base pour commencer, mais cela nécessite une page à rendre en premier dans laquelle vous pouvez simplement spécifier l'application UIA pour qu'elle fournisse UIView pour les contrôleurs de caméra/sélecteur de photos.

https://stackoverflow.com/a/28299259/1941942

Projet portable

public interface ICameraProvider
{
    Task<CameraResult> TakePhotoAsync();
    Task<CameraResult> PickPhotoAsync();
}

private Command AttachImage 
{
    var camera = await DependencyService.Get<ICameraProvider>().TakePhotoAsync();
}

projet iOS

[Assembly: Xamarin.Forms.Dependency(typeof(CameraProvider))]

public class CameraProvider : ICameraProvider
{
    private UIImagePickerController _imagePicker;
    private CameraResult _result;
    private static TaskCompletionSource<CameraResult> _tcs;

    public async Task<CameraResult> TakePhotoAsync()
    {
        _tcs = new TaskCompletionSource<CameraResult>();

        _imagePicker = new UIImagePickerController { SourceType = UIImagePickerControllerSourceType.Camera };

        _imagePicker.FinishedPickingMedia += (sender, e) =>
        {
            _result = new CameraResult();
            var filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "tmp.png");
            var image = (UIImage)e.Info.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage"));

            _result.ImageSource = ImageSource.FromStream(() => new MemoryStream(image.AsPNG().ToArray())); 
            _result.ImageBytes = image.AsPNG().ToArray();
            _result.FilePath = filepath;

            _tcs.TrySetResult(_result);
            _imagePicker.DismissViewController(true, null);
        };

        _imagePicker.Canceled += (sender, e) =>
        {
            UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
        };

        await UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewControllerAsync(_imagePicker, true);

        return await _tcs.Task; 
    }

    public async Task<CameraResult> PickPhotoAsync()
    {
        _tcs = new TaskCompletionSource<CameraResult>();

        _imagePicker = new UIImagePickerController
        {
            SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
            MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary)
        };

        _imagePicker.FinishedPickingMedia += (sender, e) =>
        {

            if (e.Info[UIImagePickerController.MediaType].ToString() == "public.image")
            {
                var filepath = (e.Info[new NSString("UIImagePickerControllerReferenceUrl")] as NSUrl);
                var image = (UIImage)e.Info.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage"));
                //var image = e.Info[UIImagePickerController.OriginalImage] as UIImage;

                _result.ImageSource = ImageSource.FromStream(() => new MemoryStream(image.AsPNG().ToArray()));
                _result.ImageBytes = image.AsPNG().ToArray();
                _result.FilePath = filepath?.Path;
            }

            _tcs.TrySetResult(_result);
            _imagePicker.DismissViewController(true, null);
        };

        _imagePicker.Canceled += (sender, e) =>
        {
            UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
        };

        await UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewControllerAsync(_imagePicker, true);

        return await _tcs.Task;
    }
}
1
pampi