c# - entry - Acceso a la cámara con Xamarin. Formas
xamarin cross platform take photo (4)
¿Alguien puede dar un ejemplo breve y autónomo sobre cómo acceder a la cámara con Xamarin.Forms 1.3.x? Simplemente llamar a la aplicación de cámara nativa y recuperar la imagen resultante sería genial. ¡Mostrar una vista en vivo en la página de Xamarin.Forms sería increíble!
Ya intenté usar Xamarin.Mobile y Xamarin.Forms.Labs, pero no conseguí que ninguna solución funcionara en ambas plataformas (centrada en Android y iOS por ahora). La mayoría de los fragmentos de código encontrados en la web (incluido el flujo de apilamiento) están incompletos, por ejemplo, no muestran la implementación de un objeto IMediaPicker o dónde anclar el método para tomar fotografías.
Aquí es cómo puede hacerlo en Xamarin Forms cross Xamarin iOS.
Esta es una buena base para comenzar, pero requiere una Página para renderizar primero, en la que simplemente puede especificar la aplicación UIA para que proporcione los UIView para los controladores de Cámara / Selector de fotos.
https://.com/a/28299259/1941942
Proyecto portatil
public interface ICameraProvider
{
Task<CameraResult> TakePhotoAsync();
Task<CameraResult> PickPhotoAsync();
}
private Command AttachImage
{
var camera = await DependencyService.Get<ICameraProvider>().TakePhotoAsync();
}
Proyecto 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;
}
}
Esto es lo que necesitaba para ejecutar la captura asíncrona de la cámara en mi aplicación:
En 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;
}
Y si necesita la rutina de autorización, asegúrese de completar también el uso de la cámara en info.plist
, y esta es la función para obtener la autorización:
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;
}
En 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;
}
}
Finalmente creé una solución mínima para iOS y Android.
El proyecto compartido
Primero, echemos un vistazo al código compartido. Para una interacción fácil entre la clase de App
compartida y el código específico de la plataforma, almacenamos una Instance
estática dentro de la public static App
:
public static App Instance;
Además, mostraremos una Image
, que se rellenará con el contenido más adelante. Entonces creamos un miembro:
readonly Image image = new Image();
Dentro del constructor de la App
, almacenamos la Instance
y creamos el contenido de la página, que es un button
simple y la image
mencionada anteriormente:
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,
},
},
};
}
El controlador de clic del botón llama al evento ShouldTakePicture
. Es un miembro público y las partes de código específicas de la plataforma se le asignarán más adelante.
public event Action ShouldTakePicture = () => {};
Finalmente, ofrecemos un método público para mostrar la imagen capturada:
public void ShowImage(string filepath)
{
image.Source = ImageSource.FromFile(filepath);
}
El proyecto android
En Android modificamos la MainActivity
. Primero, definimos una ruta para el archivo de imagen capturado:
static readonly File file = new File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures), "tmp.jpg");
Al final de OnCreate
, podemos usar la Instance
estática de la App
creada y asignar un controlador de eventos anónimo, que iniciará un nuevo Intent
de capturar una imagen:
App.Instance.ShouldTakePicture += () => {
var intent = new Intent(MediaStore.ActionImageCapture);
intent.PutExtra(MediaStore.ExtraOutput, Uri.FromFile(file));
StartActivityForResult(intent, 0);
};
Por último, pero no menos importante, nuestra actividad tiene que reaccionar en la imagen resultante. Simplemente empujará su ruta de archivo al método ShowImage
compartido.
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
App.Instance.ShowImage(file.Path);
}
¡Eso es todo! ¡No olvide configurar la "Cámara" y el permiso "WriteExternalStorage" dentro de "AndroidManifest.xml"!
El proyecto iOS
Para la implementación de iOS creamos un renderizador personalizado. Por lo tanto, agregamos un nuevo archivo "CustomContentPageRenderer" y agregamos el atributo de ensamblaje correspondiente justo después de las declaraciones de uso:
[assembly:ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))]
El CustomContentPageRenderer
hereda de PageRenderer
:
public class CustomContentPageRenderer: PageRenderer
{
...
}
ViewDidAppear
método ViewDidAppear
y agregamos las siguientes partes.
Cree un nuevo controlador de selección de imágenes refiriéndose a la cámara:
var imagePicker = new UIImagePickerController { SourceType = UIImagePickerControllerSourceType.Camera };
Presente el controlador del selector de imagen, tan pronto como se ShouldTakePicture
evento ShouldTakePicture
:
App.Instance.ShouldTakePicture += () => PresentViewController(imagePicker, true, null);
Después de tomar la foto, MyDocuments
carpeta MyDocuments
y llama al método ShowImage
compartido:
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);
};
Y, por último, tenemos que manejar una cancelación del proceso de toma de imágenes:
imagePicker.Canceled += (sender, e) => DismissViewController(true, null);
Pruebe MediaPlugin James Montemagno.
Puede instalar el complemento mediante la Consola del administrador de paquetes simplemente escribiendo y ejecutando Install-Package Xam.Plugin.Media -Version 2.6.2
o vaya a Administrar paquetes de NuGet ... y escriba Xam.Plugin.Media
e instale el complemento. (Los complementos deben instalarse en todos sus proyectos, incluidos los proyectos del cliente)
Se le solicitará un archivo readme.txt y siga las instrucciones allí. Después de eso, agregue los siguientes códigos (según sea necesario) a su proyecto compartido. Las instrucciones a seguir en el archivo readme.txt anterior son las siguientes.
Para Android Project
En su BaseActivity o MainActivity (para Xamarin.Forms) agregue este código:
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
También debe agregar algunos archivos de configuración adicionales para cumplir con el nuevo modo estricto:
Agregue lo siguiente a su AndroidManifest.xml dentro de las etiquetas < aplicación >:
<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 debe configurarse con el nombre del paquete de tu aplicación!
Agregue una nueva carpeta llamada xml a su carpeta de Recursos y agregue un nuevo archivo XML llamado
file_paths.xml
Agregue el siguiente código:
<?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 debe configurarse con el nombre del paquete de tu aplicación!
Para el proyecto iOS
Se requiere que su aplicación tenga las claves en su lista de información para NSCameraUsageDescription
y NSPhotoLibraryUsageDescription
para acceder a la cámara del dispositivo y la biblioteca de fotos / videos. Si está utilizando las capacidades de video de la biblioteca, también debe agregar NSMicrophoneUsageDescription
. La cadena que proporcione para cada una de estas claves se mostrará al usuario cuando se le solicite que proporcione permiso para acceder a estas funciones del dispositivo.
Como:
<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>
Para proyecto compartido
Para abrir simplemente la cámara, guardar la foto y mostrar una alerta con la ruta del archivo, ingrese lo siguiente en el proyecto compartido.
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");