I am building an app, that allows user to take a picture, extract meta data and attach photo to Email + meta data information.
However the issue is that If you capture a photo from camera, the event does not return a ReferenceUrl, atleast I dont see one. I do get some form of URL but that doesn't work as intended. Here is my code:-
// Take a photo with the Camera //
partial void BtnCamera_TouchUpInside(UIButton sender)
{
UIImagePickerController imagePicker = new UIImagePickerController();
imagePicker.PrefersStatusBarHidden();
imagePicker.SourceType = UIImagePickerControllerSourceType.Camera;
// handle saving picture and extracting meta-data from picture //
imagePicker.FinishedPickingMedia += Handle_FinishedPickingMedia;
// present //
PresentViewController(imagePicker, true, () => { });
}
To save the image I call the SaveImageToPhone() from my FinishedPickingMedia handler. It looks like this:-
private static void SaveImagetoPhone(UIImagePickerMediaPickedEventArgs e)
{
NSUrl url = null;
void ImageData(PHAsset asset)
{
if (asset == null)
throw new Exception("PHAsset is null");
//PHImageManager.DefaultManager.RequestImageData(asset, null, (data, dataUti, orientation, info) =>
// {
// var urlX = info.ValueForKey(new NSString("PHImageFileURLKey"));
// url = info.ValueForKey(new NSString("PHImageFileURLKey")) as NSUrl;
// DataClass._fileURL = urlX.ToString();
// });
}
PHAsset phAsset;
if (e.ReferenceUrl == null)
{
e.OriginalImage?.SaveToPhotosAlbum((image, error) =>
{
if (error == null)
{
var options = new PHFetchOptions
{
FetchLimit = 1,
SortDescriptors = new[] { new NSSortDescriptor("creationDate", true) }
};
phAsset = PHAsset.FetchAssets(options).LastOrDefault() as PHAsset;
ImageData(phAsset);
}
});
}
else
{
phAsset = PHAsset.FetchAssets(new[] { e.ReferenceUrl }, null).FirstOrDefault() as PHAsset;
ImageData(phAsset);
}
}
In this function I have a couple of lines commented out, it is here where I was trying to get the reference URL for the Photo (unsuccessfully).
And finally my email () looks like this:-
partial void BtnMessageDone_TouchUpInside(UIButton sender)
{
MFMailComposeViewController mailController;
if (MFMailComposeViewController.CanSendMail)
{
StringBuilder htmlBodyMail = FormatEmailBody();
mailController = new MFMailComposeViewController();
// do mail operations here
mailController.SetToRecipients(new string[] { "xxyyzz#...com" });
mailController.SetSubject("mail test");
mailController.SetMessageBody(htmlBodyMail.ToString(), false);
UIImage img = UIImage.FromFile(DataClass._fileURL);
mailController.AddAttachmentData(img.AsJPEG(), "image/JPG", "Image.JPG");
mailController.Finished += (object s, MFComposeResultEventArgs args) =>
{
Console.WriteLine(args.Result.ToString());
args.Controller.DismissViewController(true, null);
};
this.PresentViewController(mailController, true, null);
}
Does anyone have any pointers on how to get the absolute path/reference URL of the photo taken by the in app camera and attach it to the email body ?
Use the Path property on the NSUrl class:
NSString urlString = new NSString("file:///stack/over%20flow/foobar.txt");
NSUrl myFileUrl = new NSUrl (urlString);
Console.WriteLine (myFileUrl.AbsoluteString);
string absPath = myFileUrl.Path;
Console.WriteLine (absPath);
Related
I'm using a UIImagePickerController to select a photo and later edit it using TOCropViewController:
if (UIImagePickerController.IsSourceTypeAvailable(UIImagePickerControllerSourceType.PhotoLibrary))
{
var picker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
WeakDelegate = this
};
PresentViewController(picker, true, () =>
{
});
}
Most of the times it works correctly, but for some images didFinishPickingMediaWithInfo is not returning UIImagePickerControllerOriginalImage.
[Export("imagePickerController:didFinishPickingMediaWithInfo:")]
private void FinishedPickingMedia(UIImagePickerController picker, NSDictionary dic)
{
//Here img is sometimes null, because UIImagePickerControllerOriginalImage is not found
var img = dic.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage")) as UIImage;
picker.DismissViewController(true, () =>
{
try
{
_cropViewController = new TOCropViewController(TOCropViewCroppingStyle.Default, img)
{
AspectRatioLockEnabled = true,
AspectRatioPickerButtonHidden = true,
AspectRatioPreset = TOCropViewControllerAspectRatioPreset.Square,
Delegate = _croppingDelegate,
ResetAspectRatioEnabled = false
};
PresentViewController(_cropViewController, true, null);
}
catch (Exception ex)
{
Crashes.TrackError(ex);
}
});
}
So for example when things are ok I'm getting a result such as:
"UIImagePickerControllerMediaType" = "public.image";
"UIImagePickerControllerOriginalImage" = <UIImage:0x280a3c360 anonymous {820, 512}>;
"UIImagePickerControllerReferenceURL" = assets-library://asset/asset.JPG?id=4EDD6F79-865E-48C9-AB91-99E58E5F323B&ext=JPG;
"UIImagePickerControllerImageURL" = file:///private/var/mobile/Containers/Data/Application/8FB0FD8E-98A4-4C28-9687-E33BCE53D460/tmp/2107BB84-ECF3-47E0-9538-B380B29E7BE9.jpeg;
When I'm not getting the UIImagePickerControllerOriginalImage the result is:
"UIImagePickerControllerMediaType" = "public.image";
"UIImagePickerControllerReferenceURL" = assets-library://asset/asset.JPG?id=567381A3-D0AC-4124-9CCD-C526E0074F83&ext=JPG;
And I don't get a UIImage to edit.
What can cause this behaviour and what can be done to resolve it?
I am using my first Xamarin iOS app to take a picture, extract meta data of the picture, attach the picture to an email body and send email.
// (1.) Take a photo with the Camera //
partial void BtnCamera_TouchUpInside(UIButton sender)
{
UIImagePickerController imagePicker = new UIImagePickerController();
imagePicker.PrefersStatusBarHidden();
imagePicker.SourceType = UIImagePickerControllerSourceType.Camera;
// handle saving picture and extracting meta-data from picture //
imagePicker.FinishedPickingMedia += Handle_FinishedPickingMedia;
// present //
PresentViewController(imagePicker, true, () => { });
}
// (2.) Saves the image to the phone and then extracts metadata //
protected void Handle_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{
//NSUrl url = null;
try
{
#region Save Image and Get Meta-data
// Save Image before processing for meta-data //
SaveImagetoPhone(e);
// Get meta-data from saved image //
GetImageMetaData(e);
// (3.) Save picture to the phone and extract photo url //
private static void SaveImagetoPhone(UIImagePickerMediaPickedEventArgs e)
{
NSUrl url = null;
void ImageData(PHAsset asset)
{
if (asset == null)
throw new Exception("PHAsset is null");
PHImageManager.DefaultManager.RequestImageData(asset, null, (data, dataUti, orientation, info) =>
{
url = info.ValueForKey(new NSString("PHImageFileURLKey")) as NSUrl;
PhotoDataClass._file = url.Path;
}); }
PHAsset phAsset;
if (e.ReferenceUrl == null)
{
e.OriginalImage?.SaveToPhotosAlbum((image, error) =>
{
if (error == null)
{
var options = new PHFetchOptions
{
FetchLimit = 1,
SortDescriptors = new[] { new NSSortDescriptor("creationDate", true) }
};
phAsset = PHAsset.FetchAssets(options).LastOrDefault() as PHAsset;
ImageData(phAsset);
}
});
}
else
{
phAsset = PHAsset.FetchAssets(new[] { e.ReferenceUrl }, null).FirstOrDefault() as PHAsset;
ImageData(phAsset);
}
}
At this point I have successfully extracted the URL for the file:-
"/var/mobile/Media/DCIM/100APPLE/IMG_0036.JPG"
However when I run my email function, even though the url is populated, I get a null exception without any explanation that I know of.
// (4.) Email functionality //
https://developer.xamarin.com/recipes/ios/shared_resources/email/send_an_email/
partial void BtnMessageDone_TouchUpInside(UIButton sender)
{
MFMailComposeViewController mailController;
if (MFMailComposeViewController.CanSendMail)
{
StringBuilder htmlBodyMail = FormatEmailBody();
mailController = new MFMailComposeViewController();
// do mail operations here
mailController.SetToRecipients(new string[] { "xxx.yy#email.com" });
mailController.SetSubject("mail test");
mailController.SetMessageBody(htmlBodyMail.ToString(), false);
UIImage img = UIImage.FromFile(PhotoDataClass._file);
mailController.AddAttachmentData(img.AsJPEG(), "image/JPG", "Image.JPG");
mailController.Finished += (object s, MFComposeResultEventArgs args) =>
{
Console.WriteLine(args.Result.ToString());
args.Controller.DismissViewController(true, null);
};
this.PresentViewController(mailController, true, null);
}
}
The null exception occurs on the line inside the email functionality:-
mailController.AddAttachmentData(img.AsJPEG(), "image/JPG", "Image.JPG");
From your code, the image will be null when you use UIImage img = UIImage.FromFile(PhotoDataClass._file);. So this null exception will be thrown.
We should use PHAsset to retrieve image from system photo library instead of using the url directly. You can store the LocalIdentifier to the class PhotoDataClass, then retrieve the PHAsset through this identifier when you want to use it later.
Modify the ImageData method:
void ImageData(PHAsset asset)
{
if (asset == null)
throw new Exception("PHAsset is null");
PhotoDataClass.AssetIdentifier = asset.LocalIdentifier;
}
Then BtnMessageDone_TouchUpInside method can be:
MFMailComposeViewController mailController;
if (MFMailComposeViewController.CanSendMail)
{
var results = PHAsset.FetchAssetsUsingLocalIdentifiers(new string[] { PhotoDataClass.AssetIdentifier }, null);
foreach (PHAsset asset in results)
{
if (asset.LocalIdentifier == PhotoDataClass.AssetIdentifier)
{
PHImageManager.DefaultManager.RequestImageData(asset, null, (data, dataUti, orientation, info) =>
{
StringBuilder htmlBodyMail = FormatEmailBody();
mailController = new MFMailComposeViewController();
// do mail operations here
...
UIImage img = UIImage.LoadFromData(data);
mailController.AddAttachmentData(img.AsJPEG(), "image/JPG", "Image.JPG");
...
});
}
}
}
Moreover That LastOrDefault() is not what I think it is
This is because your NSSortDescriptor, use SortDescriptors = new[] { new NSSortDescriptor("creationDate", false) } or phAsset = PHAsset.FetchAssets(options).FirstOrDefault() as PHAsset; to get your photo you just capture from your camera.
I am taking a picture with the iOS camera and trying to extract metadata from the image. This is my code:-
partial void BtnCamera_TouchUpInside(UIButton sender)
{
UIImagePickerController imagePicker = new UIImagePickerController();
imagePicker.PrefersStatusBarHidden();
imagePicker.SourceType = UIImagePickerControllerSourceType.Camera;
// handle saving picture and extracting meta-data from picture //
imagePicker.FinishedPickingMedia += Handle_FinishedPickingMedia;
// present //
PresentViewController(imagePicker, true, () => { });
}
protected void Handle_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{
try
{
// determine what was selected, video or image
bool isImage = false;
switch (e.Info[UIImagePickerController.MediaType].ToString())
{
case "public.image":
isImage = true;
break;
}
// get common info
NSUrl referenceURL = e.Info[new NSString("UIImagePickerControllerReferenceURL")] as NSUrl;
if (referenceURL != null)
Console.WriteLine("Url:" + referenceURL.ToString());
I am able to initiate the camera, take the picture and then however when I click 'use photo'... The referenceURL comes back as NULL... How can I get the url, such that to extract GPS coordinates of the photo and such other attributes ?
I had a tremendous amount of trouble with the URL. It can be a file, it can be a web url and it acts different on every device. My app crashed and burned so many times with my test group. I finally found a way to get the Metadata from the data. There are multiple ways to get the DateTaken, Width and Height as well as GPS Coordinates. In addition, I needed the Camera MFG and the Model.
string dateTaken = string.Empty;
string lat = string.Empty;
string lon = string.Empty;
string width = string.Empty;
string height = string.Empty;
string mfg = string.Empty;
string model = string.Empty;
PHImageManager.DefaultManager.RequestImageData(asset, options, (data, dataUti, orientation, info) => {
dateTaken = asset.CreationDate.ToString();
// GPS Coordinates
var coord = asset.Location?.Coordinate;
if (coord != null)
{
lat = asset.Location?.Coordinate.Latitude.ToString();
lon = asset.Location?.Coordinate.Longitude.ToString();
}
UIImage img = UIImage.LoadFromData(data);
if (img.CGImage != null)
{
width = img.CGImage?.Width.ToString();
height = img.CGImage?.Height.ToString();
}
using (CGImageSource imageSource = CGImageSource.FromData(data, null))
{
if (imageSource != null)
{
var ns = new NSDictionary();
var imageProperties = imageSource.CopyProperties(ns, 0);
if (imageProperties != null)
{
width = ReturnStringIfNull(imageProperties[CGImageProperties.PixelWidth]);
height = ReturnStringIfNull(imageProperties[CGImageProperties.PixelHeight]);
var tiff = imageProperties.ObjectForKey(CGImageProperties.TIFFDictionary) as NSDictionary;
if (tiff != null)
{
mfg = ReturnStringIfNull(tiff[CGImageProperties.TIFFMake]);
model = ReturnStringIfNull(tiff[CGImageProperties.TIFFModel]);
//dateTaken = ReturnStringIfNull(tiff[CGImageProperties.TIFFDateTime]);
}
}
}
}
}
}
The little helper function
private string ReturnStringIfNull(NSObject inObj)
{
if (inObj == null) return String.Empty;
return inObj.ToString();
}
You can request a PHAsset from the reference Url and that will contain some metadata. You can request the image data to obtain more.
Note: If you need full EXIF, you need to check to ensure the image on on the device (could be iCloud-based), download it if needed, and then load the image data with the ImageIO framework (lots of SO postings cover this).
public void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{
void ImageData(PHAsset asset)
{
if (asset == null) throw new Exception("PHAsset is null");
PHImageManager.DefaultManager.RequestImageData(asset, null, (data, dataUti, orientation, info) =>
{
Console.WriteLine(data);
Console.WriteLine(info);
});
}
PHAsset phAsset;
if (e.ReferenceUrl == null)
{
e.OriginalImage?.SaveToPhotosAlbum((image, error) =>
{
if (error == null)
{
var options = new PHFetchOptions
{
FetchLimit = 1,
SortDescriptors = new[] { new NSSortDescriptor("creationDate", true) }
};
phAsset = PHAsset.FetchAssets(options).FirstOrDefault() as PHAsset;
ImageData(phAsset);
}
});
}
else
{
phAsset = PHAsset.FetchAssets(new[] { e.ReferenceUrl }, null).FirstOrDefault() as PHAsset;
ImageData(phAsset);
}
}
Note: Make sure you have request runtime photo library authorization PHPhotoLibrary.RequestAuthorization) and have set the Privacy - Photo Library Usage Description string in your info.plist to avoid a nasty privacy crash
I am trying to add Instagram in "Share To" functionality in my app. I have seen the Instagram's iPhone hooks documents. I have created custom UIActivty which works fine but my question is, is there a way to add "Import with Instagram" functionality as it can be seen in iOS's Photos app iOS Photo App:
In my app for some reason, it does not show that "Import with Instagram". my app Share view :
I do not want to share only with Instagram so no ".igo"
EDIT: All of this is specifically for iOS versions < 10. For some reasons Instagram Share Extension works fine (for my app) in devices with iOS >= 10.
EDIT: I am trying to share image and video with ".jpeg" and ".mov" formats respectively
I have seen/read that Instagram added share extension in release 8.2, so technically all the apps should show "Instagram" in share tray, i.e. it can be seen in Google Photos app.
public void NativeShareImage(UIView sourceView, CGRect sourceRect,
UIImage image, string shareCaption, string emailSubject)
{
string filename = Path.Combine(FileSystemUtils.GetTemporaryDataPath(), "Image.jpg");
NSError err = null;
using(var imgData = image.AsJPEG(JpgImageQuality))
{
if(imgData.Save(filename, false, out err))
{
Logger.Information("Image saved before native share as {FileName}", filename);
}
else
{
Logger.Error("Image NOT saved before native share as to path {FileName}. {Error}", filename, err.Description);
return;
}
}
// this are the items that needs to be shared
// Instagram ignores the caption, that is known
var activityItems = new List<NSObject>
{
new NSString(shareCaption),
new NSUrl(new Uri(filename).AbsoluteUri)
};
// Here i add the custom UIActivity for Instagram
UIActivity[] applicationActivities =
{
new InstagramActivity(image, sourceRect, sourceView),
}
var activityViewController = new UIActivityViewController(activityItems.ToArray(), applicationActivities);
activityViewController.SetValueForKey(new NSString(emailSubject), new NSString("subject"));
activityViewController.CompletionWithItemsHandler = (activityType, completed, returnedItems, error) =>
{
UserSharedTo(activityType, completed);
};
// Hide some of the less used activity types so that Instagram shows up in the list. Otherwise it's pushed off the activity view
// and the user has to scroll to see it.
activityViewController.ExcludedActivityTypes = new[] { UIActivityType.AssignToContact, UIActivityType.CopyToPasteboard, UIActivityType.Print };
if(UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone)
{
PresentViewController(activityViewController, true, null);
}
else
{
activityViewController.ModalPresentationStyle = UIModalPresentationStyle.Popover;
PresentViewController(activityViewController, true, null);
// Get the popover presentation controller and configure it.
UIPopoverPresentationController presentationController = activityViewController.PopoverPresentationController;
presentationController.PermittedArrowDirections = UIPopoverArrowDirection.Down;
presentationController.SourceRect = sourceRect;
presentationController.SourceView = sourceView;
}
}
// when opening custom activity use ".igo" to only show instagram
public class InstagramActivity : UIActivity
{
public InstagramActivity(UIImage imageToShare, CGRect frame, UIView view, string shareCaption = "")
{
_ImageToShare = imageToShare;
_Frame = frame;
_View = view;
}
public override UIImage Image { get { return UIImage.FromBundle("Instagram"); } }
public override string Title { get { return "Instagram"; } }
public override NSString Type { get { return new NSString("PostToInstagram"); } }
public string Caption { get; set; }
public override bool CanPerform(NSObject[] activityItems)
{
return UIApplication.SharedApplication.CanOpenUrl(NSUrl.FromString("instagram://app"));
}
public override void Prepare(NSObject[] activityItems)
{
}
public override void Perform()
{
string filename = Path.Combine(FileSystemUtils.GetTemporaryDataPath(), "Image.igo");
NSError err = null;
using(var imgData = _ImageToShare.AsJPEG(JpgImageQuality))
{
if(imgData.Save(filename, false, out err))
{
Logger.Information("Instagram image saved as {FileName}", filename);
}
else
{
Logger.Error("Instagram image NOT saved as to path {FileName}. {Error}", filename, err.Description);
Finished(false);
return;
}
}
var url = NSUrl.FromFilename(filename);
_DocumentController = UIDocumentInteractionController.FromUrl(url);
_DocumentController.DidEndSendingToApplication += (o, e) => Finished(true);
_DocumentController.Uti = "com.instagram.exclusivegram";
if(!string.IsNullOrEmpty(ShareCaption))
{
_DocumentController.Annotation = NSDictionary.FromObjectAndKey(new NSString(ShareCaption), new NSString("InstagramCaption"));
}
_DocumentController.PresentOpenInMenu(_Frame, _View, true);
}
UIImage _ImageToShare;
CGRect _Frame;
UIView _View;
UIDocumentInteractionController _DocumentController;
}
I'm selecting an image form the gallery using the UIImagePickerController. After selecting an image, I would like to update the real image file path on a text field.
Can I get the file path from the referenceUrl? The referenceUrl in my case always returns null.
protected void Handle_FinishedPickingMedia (object sender, UIImagePickerMediaPickedEventArgs e)
{
try{
//determine what was selected, video or image
bool isImage = false;
switch(e.Info [UIImagePickerController.MediaType].ToString()) {
case "public.image":
Console.WriteLine("Image selected");
isImage = true;
break;
}
// get common info (shared between images and video)
NSUrl referenceURL = e.Info[new NSString("UIImagePickerControllerReferenceUrl")] as NSUrl;
if (referenceURL != null)
Console.WriteLine("Url:"+referenceURL.ToString ());
// if it was an image, get the other image info
if(isImage) {
// get the original image
UIImage originalImage = e.Info[UIImagePickerController.OriginalImage] as UIImage;
if(originalImage != null) {
// do something with the image
new Thread(new System.Threading.ThreadStart(() => {
Thread.Sleep(350);
BeginInvokeOnMainThread (() => {
var tag = ((UIButton)sender).Tag;
//UIButton senderButton = (UIButton)sender;
switch(tag)
{
case 0:
// do something here
break;
case 1:
// do something here
break;
});
})).Start();
}
}
// dismiss the picker
imagePicker.DismissModalViewController (true);
}catch(Exception ex)
{
ShowAlert ("Failed !", "Unable to select image", "");
Console.WriteLine(ex.Message + ex.StackTrace);
}
}
For anyone else facing this issue, the solution was a simple typo.I printed out the details of the NSDictionary and noticed that the "Url" part of the
UIImagePickerControllerReferenceUrl was all caps. This is what worked for me.
Change this line:
NSUrl referenceURL = e.Info[new NSString("UIImagePickerControllerReferenceUrl")] as NSUrl;
To this :
NSUrl referenceURL = e.Info[new NSString("UIImagePickerControllerReferenceURL")] as NSUrl;
To get the filename of the selected image, I added the AssestsLibrary and used that to extract the necessary metadata.
Here's my full implementation:
protected void Handle_FinishedPickingMedia (object sender, UIImagePickerMediaPickedEventArgs e)
{
try{
//determine what was selected, video or image
bool isImage = false;
switch(e.Info [UIImagePickerController.MediaType].ToString()) {
case "public.image":
Console.WriteLine("Image selected");
isImage = true;
break;
}
// get common info (shared between images and video)
NSUrl referenceURL = e.Info[new NSString("UIImagePickerControllerReferenceURL")] as NSUrl;
if (referenceURL != null)
Console.WriteLine("Url:"+referenceURL.ToString ());
ALAssetsLibrary assetsLibrary = new ALAssetsLibrary();
assetsLibrary.AssetForUrl(referenceURL,delegate (ALAsset asset){
ALAssetRepresentation representation = asset.DefaultRepresentation;
if (representation == null)
{
return;
}else{
string fileName = representation.Filename;
Console.WriteLine("Image Filename :" + fileName);
}
},delegate(NSError error) {
Console.WriteLine ("User denied access to photo Library... {0}", error);
});
// if it was an image, get the other image info
if(isImage) {
// get the original image
UIImage originalImage = e.Info[UIImagePickerController.OriginalImage] as UIImage;
if(originalImage != null) {
// do something with the image
new Thread(new System.Threading.ThreadStart(() => {
Thread.Sleep(350);
BeginInvokeOnMainThread (() => {
var tag = ((UIButton)sender).Tag;
//UIButton senderButton = (UIButton)sender;
switch(tag)
{
case 0:
// do something here
break;
case 1:
// do something here
break;
});
})).Start();
}
}
// dismiss the picker
imagePicker.DismissModalViewController (true);
}catch(Exception ex)
{
ShowAlert ("Failed !", "Unable to select image", "");
Console.WriteLine(ex.Message + ex.StackTrace);
}
}