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.
J'ai finalement créé une solution minimale pour iOS et Android.
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);
}
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"!
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);
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.
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:
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!
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!
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 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");
Voici ce dont j'avais besoin pour exécuter la capture de caméra asynchrone dans mon application:
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;
}
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;
}
}
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
public interface ICameraProvider
{
Task<CameraResult> TakePhotoAsync();
Task<CameraResult> PickPhotoAsync();
}
private Command AttachImage
{
var camera = await DependencyService.Get<ICameraProvider>().TakePhotoAsync();
}
[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;
}
}