Messaging PlugIn (Carel Lotz) in Xamarin PCL? - c#

So I have this button in my Portable Class Library that should directly attach an xml file I've created into a mail and send it using the Messaging PlugIn. The problem is that .WithAttachment() is not supported in PCL so I wanted to ask if I can get around this using DependencyService and if so, how?
Can I just return .WithAttachment() from the UWP class (as UWP is my target platform)? Wouldn't there be a conflict because I've read that the overload of .WithAttachment() in UWP is .WithAttachment(IStorageFile file).
private void Senden_Clicked(object sender, EventArgs e)
{
var emailMessenger = CrossMessaging.Current.EmailMessenger;
if (emailMessenger.CanSendEmail)
{
var email = new EmailMessageBuilder()
.To("my.address#gmail.com")
.Subject("Test")
.Body("Hello there!")
//.WithAttachment(String FilePath, string ContentType) overload showing in PCL
//.WithAttachment(IStorageFile file) overload for the UWP according to the documentation
.Build();
emailMessenger.SendEmail(email);
}
}
EDIT:
I've been able to modify Nick Zhou's answer a little bit to be able to send an email with attachment via button-click. I just changed this peace of code:
var picker = new Windows.Storage.Pickers.FileOpenPicker
{
ViewMode = Windows.Storage.Pickers.PickerViewMode.List,
SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add("*");
to this:
StorageFolder sf = ApplicationData.Current.LocalFolder;
var file = await sf.GetFileAsync("daten.xml");
Of course you then need to create the file inside the app's local folder instead of the documents library.

The problem is that .WithAttachment() is not supported in PCL so I wanted to ask if I can get around this using DependencyService and if so, how?
Of course you can use DependencyService to achieve sending email with attactment. But you could create two interface like code behind.
SendEmail Interface
public interface IMessageEmail
{
void SendEmailMehod(string address, string subject, string body, StorageFile attactment = null);
}
IMessageEmail implementation in UWP project.
public void SendEmailMehod(string address, string subject, string body, StorageFile attactment = null)
{
var emailMessenger = CrossMessaging.Current.EmailMessenger;
if (emailMessenger.CanSendEmail)
{
if (attactment != null)
{
var email = new EmailMessageBuilder()
.To(address)
.Subject(subject)
.Body(body)
.WithAttachment(attactment)
.Build();
emailMessenger.SendEmail(email);
}
else
{
var email = new EmailMessageBuilder()
.To(address)
.Subject(subject)
.Body(body)
.Build();
emailMessenger.SendEmail(email);
}
}
}
As you can see the .WithAttachment(attactment) parameter is IStorageFile. So you need pass a file to the method. Hence you could create another DependencyService.
IFilePicker Interface
public interface IFilePicker
{
Task<StorageFile> getFileAsync();
}
IMessageEmail implementation in UWP project.
public async Task<StorageFile> getFileAsync()
{
var picker = new Windows.Storage.Pickers.FileOpenPicker
{
ViewMode = Windows.Storage.Pickers.PickerViewMode.List,
SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add("*");
var file = await picker.PickSingleFileAsync();
if (file != null)
{
return file;
}
return null;
}
You can try the project I have upload to github.

Related

Maui media picker on Windows Platform error

I have created a very basic MauiApp because I wanted to try the MediaPicker on the Windows Platform.
Thus I copied the code from the official documentation and tried to run my application
However if I add <uap:Capability Name="webcam"/> to the Package.appxmanifest file as suggested in the documentaion, and run the application it gives me the following error:
Error DEP0700: Registration of the app failed. [0x80080204] error 0xC00CE169: App
manifest validation error: The app manifest must be valid as per schema: Line 39, Column
21, Reason: 'webcam' violates enumeration constraint of 'documentsLibrary
picturesLibrary videosLibrary musicLibrary enterpriseAuthentication
sharedUserCertificates userAccountInformation removableStorage appointments contacts
phoneCall blockedChatMessages objects3D voipCall chat'.
The attribute 'Name' with value 'webcam' failed to parse. MauiApp3
So in order to solve this problem I tried to change the capability from <uap:Capability Name="webcam"/> to <DeviceCapability Name="webcam"/>.
In this way I can run the application without errors, but photo is always null:
public async void TakePhoto(object sender, EventArgs e)
{
if (MediaPicker.Default.IsCaptureSupported)
{
FileResult photo = await MediaPicker.Default.CapturePhotoAsync();
if (photo != null)
{
// save the file into local storage
string localFilePath = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
using Stream sourceStream = await photo.OpenReadAsync();
using FileStream localFileStream = File.OpenWrite(localFilePath);
await sourceStream.CopyToAsync(localFileStream);
}
else
{
// *** IT ALWAYS ENTERS IN THE ELSE CLAUSE ***
// *** BECAUSE photo IS ALWAYS NULL ***
CounterBtn.Text = $"Capture is supported but {photo} is null";
}
}
}
Note: The function above is called when I click to this button that I've defined in MainPage.xaml file:
<Button
x:Name="ImageBtn"
Text="Take Photo"
SemanticProperties.Hint="Take Image"
Clicked="TakePhoto"
HorizontalOptions="Center" />
Unfortunately this is a old and known bug for WinUI3.
microsoft/WindowsAppSDK#1034
As a workaround you can create a custom mediapicker that inherit everything from the MAUI one for Android, iOS and Catalyst.
Then implement a custom mediapicker for Windows, intheriting all the Capture... methods and reimplementing the CapturePhotoAsync using a custom class for camera capture:
public async Task<FileResult> CaptureAsync(MediaPickerOptions options, bool photo)
{
var captureUi = new CameraCaptureUI(options);
var file = await captureUi.CaptureFileAsync(photo ? CameraCaptureUIMode.Photo : CameraCaptureUIMode.Video);
if (file != null)
return new FileResult(file.Path,file.ContentType);
return null;
}
This is the new CameraCaptureUI class:
public async Task<StorageFile> CaptureFileAsync(CameraCaptureUIMode mode)
{
var extension = mode == CameraCaptureUIMode.Photo ? ".jpg" : ".mp4";
var currentAppData = ApplicationData.Current;
var tempLocation = currentAppData.LocalCacheFolder;
var tempFileName = $"CCapture{extension}";
var tempFile = await tempLocation.CreateFileAsync(tempFileName, CreationCollisionOption.GenerateUniqueName);
var token = Windows.ApplicationModel.DataTransfer.SharedStorageAccessManager.AddFile(tempFile);
var set = new ValueSet();
if (mode == CameraCaptureUIMode.Photo)
{
set.Add("MediaType", "photo");
set.Add("PhotoFileToken", token);
}
else
{
set.Add("MediaType", "video");
set.Add("VideoFileToken", token);
}
var uri = new Uri("microsoft.windows.camera.picker:");
var result = await Windows.System.Launcher.LaunchUriForResultsAsync(uri, _launcherOptions, set);
if (result.Status == LaunchUriStatus.Success && result.Result != null)
{
return tempFile;
}
return null;
}
Also in the Package.appxmanifest add these capabilities:
<DeviceCapability Name="microphone"/>
<DeviceCapability Name="webcam"/>
Credits for the CameraCapture class
Edit:
Modified code to allow video capturing, credits here

Xamarin iOS post image to Instagram

Goal: Using an iOS native method, push a user made picture onto their Instagram feed in C#.
public bool ShareImage(byte[] imageByte)
{
bool result = false;
//string fileName = "xxx.png";
//string path = Path.Combine(FileSystem.CacheDirectory, fileName);
//File.WriteAllBytes(path, imageByte);
NSData data = NSData.FromArray(imageByte);
UIImage image = UIImage.LoadFromData(data);
//NSUrl url = NSUrl.FromString($"instagram://library?AssetPath={path}"); // Only opens
NSUrl url = NSUrl.FromString($"instagram://library?LocalIdentifier={1}");
if (UIApplication.SharedApplication.CanOpenUrl(url))
{
UIApplication.SharedApplication.OpenUrl(url);
}
return result;
}
As far as I'm aware the way I have to do this is to save my image to the device and get a Local Identifier for this.
I have constructed this from the snippets of objective-c code I have seen. Sharing photos only works with the native app installed, as I have learnt from my trials to get the facebook module working.
Edit: Using PHAssetChangeRequest from the iOS Photos namespace does not work.
A collegue has pointed out to me about the possibility of saving then using a photo picker to get the PHAsset for the Local Identifier. But this is an extra step I do not want the users of the App to go through. Better to just remove Instagram support as I just can go through the generic share method as shown below. The disadvantage of this method is that the user has then to pick the medium to share over.
public async void ShareImage(byte[] imageByte)
{
string fileName = "xxx.png";
string path = Path.Combine(FileSystem.CacheDirectory, fileName);
File.WriteAllBytes(path, imageByte);
await Share.RequestAsync(
new ShareFileRequest()
{
File = new ShareFile(path),
Title = "xxx"
}
);
}
Edit 2
Tried a different way using UIDocumentInteractionController but it is showing nothing and not posting, it is not throwing any exceptions to give me any clues as to what I'm doing wrong.
public bool ShareImage(byte[] imageByte)
{
bool result = false;
string fileName = "xxx.igo";
string path = Path.Combine(FileSystem.CacheDirectory, fileName);
File.WriteAllBytes(path, imageByte);
//string caption = "xxx";
string uti = "com.instagram.exclusivegram";
UIImageView view = new UIImageView();
//UIDocumentInteractionController controller = UIDocumentInteractionController.FromUrl(NSUrl.FromString(path));
UIDocumentInteractionController controller = new UIDocumentInteractionController
{
//controller.Url = NSUrl.FromString(path);
Url = NSUrl.FromFilename(path),
Uti = uti
};
//CoreGraphics.CGRect viewDimensions = new CoreGraphics.CGRect(0, 0, 200, 100);
_ = controller.PresentOpenInMenu(CoreGraphics.CGRect.Empty, view, true);
//_ = controller.PresentOpenInMenu(viewDimensions, view, true);
return result;
}
Edit 3
Using
UIView view = new UIImageView();
if (UIApplication.SharedApplication.KeyWindow.RootViewController is UIViewController uiController && uiController.View != null)
{
view = uiController.View;
}
I was able to get the possible share options to show. I was logged in to Instagram using the native App, but the post did not show.
Change
Url = NSUrl.FromFilename(path)
to
Url = new NSUrl(path,false)
The second way is pointing to the correct file path not the file name .
Change
controller.PresentOpenInMenu(CoreGraphics.CGRect.Empty, view, true);
to
controller.PresentOptionsMenu(UIScreen.MainScreen.Bounds, (UIApplication.SharedApplication.Delegate as AppDelegate).Window, true);
Show UIDocumentInteractionController inside current Window and set the proper frame.

PlanGrid API - Publish Log still prompts for version set name after UploadVersion

I'm trying to post a file to my PlanGrid project using the following code. Once the upload is complete, I log into the website, open the Publish Log, then click on "Publish Your Sheets", at which point it asks me again to define version set. Can someone clarify what the UploadVersionRequest.VersionName property is used for then?
public static async Task Upload(string project_uid, string filename, Stream payload)
{
var api = PlanGridClient.Create(Properties.Settings.Default.ApiKey);
var versionRequest = new UploadVersionRequest
{
NumberOfFiles = 1,
VersionName = "MyVersion" // how does this get used??
};
var versionUpload = await api.UploadVersion(project_uid, versionRequest);
foreach (var fileUploadRequest in versionUpload.FileUploadRequests)
{
var uploadFile = new UploadFile
{
FileName = filename
};
var fileUpload = await api.UploadFileToVersion(project_uid, versionUpload.Uid, fileUploadRequest.Uid, uploadFile);
await api.Upload<object>(fileUpload, payload);
}
await api.CompleteVersionUpload(project_uid, versionUpload.Uid);
}
Thanks for the question. We released an update to the upload process that included both version name and issue date. That change has not been reflected in the API, so when you set the version name in the API, it is not reflected during the publishing process.

Xamarin.forms how to hold image on same page after navigating another page

I have uploaded image on my profile page and I want to hold that image until I logout in xamarin forms.
My image will be lost if I select another page so I want to hold it until I log out.
var profile = new Image { };
profile.Source = "profile.png";
profile.HorizontalOptions = LayoutOptions.StartAndExpand;
profile.VerticalOptions = LayoutOptions.StartAndExpand;
var profiletap = new TapGestureRecognizer();
profiletap.Tapped += async (s, e) =>
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
im = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
//file.Dispose();
return stream;
});
profile.Source = im;
// await Navigation.PushModalAsync(new PhotoPage(im));
};
profile.GestureRecognizers.Add(profiletap);
Pages do not get destroyed when navigating to another page and coming back, which is why a page's constructor only gets executed the first time it is shown. So I am not sure what you mean when you say you want to hold that image.
Having said that, you could always assign the entire profile variable to a static global variable in your App class like below so that it stays the same no matter what. Then you would have to assign/initialize the global variable at the correct time.
But again, I am not sure if that is necessary, so you might try to explain more what the issue actually is:
In the App class:
public class App : Application {
public static Image ProfileImage = new Image {
Source = "profile.png",
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalOptions = LayoutOptions.StartAndExpand
};
....
}
Then in your page:
public class ProfilePage : ContentPage {
public ProfilePage() {
....
App.ProfileImage.GestureRecognizers.Add(profiletap);
}
}
Edit: See my answer here for an example of using a plugin to allow the user to choose a photo from their device's camera roll. Once you have the photo path, you can simply use HttpClient to send the image and a base64 string. There are plenty of other example online about how to do that.
Edit #2: After this line of your code:
var file = await CrossMedia.Current.PickPhotoAsync();
You now have the file and the path in file variable. So currently all you are doing is showing the image using ImageSource.FromStream but in order to keep showing the image when you return to the page, you need to also save the image to the device. In order to do that, you will need to write platform specific code in each project and reference that in your shared code. Something like this:
In your iOS and Android project, create a new file (FileHelper_Android.cs and FileHelper_iOS.cs for example) and add the following (the same code can be added to both iOS and Android files, just change the name of the class and file:
using ....;
[assembly: Dependency(typeof(FileHelper_Android))]
namespace YourNamespace.Droid{
/// <summary>
/// Responsible for working with files on an Android device.
/// </summary>
internal class FileHelper_Android : IFileHelper {
#region Constructor
public FileHelper_Android() { }
#endregion
public string CopyFile(string sourceFile, string destinationFilename, bool overwrite = true) {
if(!File.Exists(sourceFile)) { return string.Empty; }
string fullFileLocation = Path.Combine(Environment.GetFolderPath (Environment.SpecialFolder.Personal), destinationFilename);
File.Copy(sourceFile, fullFileLocation, overwrite);
return fullFileLocation;
}
}
}
Do the same on iOS and just change the file name. Now in your shared project you need to create IFileHelper.cs like so:
public interface IFileHelper {
string CopyFile(string sourceFile, string destinationFilename, bool overwrite = true);
}
Finally, in your page you would write the following:
_fileHelper = _fileHelper ?? DependencyService.Get<IFileHelper>();
profiletap.Tapped += async (s, e) =>
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
profile.Source = im;
imageName = "SomeUniqueFileName" + DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss-tt");
filePath = _fileHelper.CopyFile(file.Path, imageName);
im = ImageSource.FromFile(filePath)
// await Navigation.PushModalAsync(new PhotoPage(im));
};
Above, once the user chooses the file, we copy that file locally and the we also set your im variable to the new local file path, which gets returned from the IFileHelper.CopyFile method.
You still need to handle the case when the user comes back to the page or turns the app off and on again. In that situation, you need to load the saved image path. I would suggest either saving the image path into the DB, unless the user will only ever have a single profile image, then you could always just load that same path and filename. Let me know if you still have issues.

Camera access with Xamarin.Forms

Is anyone able to give a short, self-contained example on how to access the camera with Xamarin.Forms 1.3.x? Simply calling the native camera application and retrieving the resulting picture would be great. Displaying a live view on the Xamarin.Forms page would be awesome!
I already tried to use Xamarin.Mobile and Xamarin.Forms.Labs, but I couldn't get any solution to work on both platforms (focussing on Android and iOS for now). Most code snippets found on the web (including stackoverflow) are incomplete, e.g. not showing the implementation of an IMediaPicker object or where to anchor the method for taking pictures.
I finally created a minimum solution for iOS and Android.
The shared project
First, let's look into the shared code. For an easy interaction between the shared App class and the platform-specific code we store a static Instance within the public static App:
public static App Instance;
Furthermore, we will display an Image, which will be filled with content later. So we create a member:
readonly Image image = new Image();
Within the App constructor we store the Instance and create the page content, which is a simple button and the aforementioned image:
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,
},
},
};
}
The button's click handler calls the event ShouldTakePicture.
It is a public member and the platform-specific code parts will assign to it later on.
public event Action ShouldTakePicture = () => {};
Finally, we offer a public method for displaying the captured image:
public void ShowImage(string filepath)
{
image.Source = ImageSource.FromFile(filepath);
}
The Android project
On Android we modify the MainActivity.
First, we define a path for the captured image file:
static readonly File file = new File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures), "tmp.jpg");
At the end of OnCreate we can use the static Instance of the created App and assign an anonymous event handler, which will start a new Intent for capturing an image:
App.Instance.ShouldTakePicture += () => {
var intent = new Intent(MediaStore.ActionImageCapture);
intent.PutExtra(MediaStore.ExtraOutput, Uri.FromFile(file));
StartActivityForResult(intent, 0);
};
Last but not least, our activity has to react on the resulting image. It will simply push its file path to the shared ShowImage method.
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
App.Instance.ShowImage(file.Path);
}
That's about it!
Just don't forget to set the "Camera" and the "WriteExternalStorage" permission within "AndroidManifest.xml"!
The iOS project
For the iOS implementation we create a custom renderer.
Therefore, we add a new file "CustomContentPageRenderer" and add the corresponding assembly attribute right after the using statements:
[assembly:ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))]
The CustomContentPageRenderer inherits from PageRenderer:
public class CustomContentPageRenderer: PageRenderer
{
...
}
We override the ViewDidAppear method and add the following parts.
Create a new image picker controller referring to the camera:
var imagePicker = new UIImagePickerController { SourceType = UIImagePickerControllerSourceType.Camera };
Present the image picker controller, as soon as the ShouldTakePicture event is raised:
App.Instance.ShouldTakePicture += () => PresentViewController(imagePicker, true, null);
After taking the picture, save it to the MyDocuments folder and call the shared ShowImage method:
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);
};
And finally, we need to handle a cancellation of the image taking process:
imagePicker.Canceled += (sender, e) => DismissViewController(true, null);
Try out James Montemagno's MediaPlugin.
You can install the plugin using Package Manager Console by simply typing and running Install-Package Xam.Plugin.Media -Version 2.6.2 or else go to Manage NuGet Packages... and type Xam.Plugin.Media and install the plugin. (The plugins has to be installed in all your projects - including the client projects)
A readme.txt will be prompted and follow the instructions there. After that, add the following codes (as required) to your shared project. The instructions to be followed in the above readme.txt file are as follows.
For Android Project
In your BaseActivity or MainActivity (for Xamarin.Forms) add this code:
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
You must also add a few additional configuration files to adhere to the new strict mode:
Add the following to your AndroidManifest.xml inside the <application> tags:
<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 must be set to your app package name!
Add a new folder called xml into your Resources folder and add a new XML file called file_paths.xml
Add the following code:
<?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 must be set to your app package name!
For iOS Project
Your app is required to have keys in your Info.plist for NSCameraUsageDescription and NSPhotoLibraryUsageDescription in order to access the device's camera and photo/video library. If you are using the Video capabilities of the library then you must also add NSMicrophoneUsageDescription. The string that you provide for each of these keys will be displayed to the user when they are prompted to provide permission to access these device features.
Such as:
<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>
For Shared Project
To simply open camera, save photo and display an alert with the file path, enter the following to the shared project.
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");
Here is how you can do it on Xamarin Forms cross Xamarin iOS.
This is a good base to start but it requires a Page to rendered first in which you could just specify the UIApplication for it to provide the UIView for Camera / Photo Picker controllers.
https://stackoverflow.com/a/28299259/1941942
Portable Project
public interface ICameraProvider
{
Task<CameraResult> TakePhotoAsync();
Task<CameraResult> PickPhotoAsync();
}
private Command AttachImage
{
var camera = await DependencyService.Get<ICameraProvider>().TakePhotoAsync();
}
iOS Project
[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;
}
}
Here's what I needed to get async camera-capture running in my app:
In 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;
}
And if you need the authorization routine, make sure you also fill out the camera use in info.plist too, and here's the function to get authorization:
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;
}
In 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;
}
}

Categories