I want to use UIActivityViewController to share files from my iOS app. The main question for me is how do I handle different file types.
What I'v got so far:
Images
public void OpenInExternalApp(string filepath)
{
if (!File.Exists(filepath))
return;
UIImage uiImage = UIImage.FromFile(filepath);
// Define the content to share
var activityItems = new NSObject[] { uiImage };
UIActivity[] applicationActivities = null;
var activityController = new UIActivityViewController(activityItems, applicationActivities);
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone)
{
// Phone
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(activityController, true, null);
}
else
{
// Tablet
var popup = new UIPopoverController(activityController);
UIView view = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
CGRect rect = new CGRect(view.Frame.Width/2, view.Frame.Height, 50, 50);
popup.PresentFromRect(rect, view, UIPopoverArrowDirection.Any, true);
}
}
Don't know if from the memory management aspect it is a good idea to load the image at once. What will happen if the image is too big for holding it completely in RAM? See here for example.
Strings
var activityItems = new NSObject[] { UIActivity.FromObject(new NSString(text)) };
Only text.
NSUrl
NSUrl url = NSUrl.CreateFileUrl(filepath, false, null);
Here in most cases the same app appear. But for example the PDF reader doesn't appear for a PDF file. The preview in mail on the other side shows Adobe Acrobat.
Everything
var activityItems = new NSObject[] { NSData.FromFile(filepath) };
The last approach has the disadvantage that not all apps are displayed, which for example could open a PDF file. Also this applies.
I want to use all types of files. I don't think a subclass of UIActivity would help here. Perhaps a sublcass of UIActivityItemProvider?
Side note: You can also post your solutions in Objective C/Swift.
I tried to implement UIActivityItemProvider, but here again not all apps where shown for the corresponding filetype. E.g. for a docx-document Word was not shown.
Now I switched to UIDocumentInteractionController and now there are many apps available.
UIDocumentInteractionController documentController = new UIDocumentInteractionController();
documentController.Url = new NSUrl(filepath, false);
string fileExtension = Path.GetExtension(filepath).Substring(1);
string uti = UTType.CreatePreferredIdentifier(UTType.TagClassFilenameExtension.ToString(), fileExtension, null);
documentController.Uti = uti;
UIView presentingView = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
documentController.PresentOpenInMenu(CGRect.Empty, presentingView, true);
Imho there are too many apps, because the file type xml should not be really be supported by a PDF reader, but it is. Nevertheless, it seems to work now thanks to this post:
In general if you’re sharing an image or url, you might want to use a UIActivityViewController. If you’re sharing a document, you might want to use a UIDocumentInteractionController.
Related
I want to use UIActivityViewController to share files from my iOS app. The main question for me is how do I handle different file types.
What I'v got so far:
Images
public void OpenInExternalApp(string filepath)
{
if (!File.Exists(filepath))
return;
UIImage uiImage = UIImage.FromFile(filepath);
// Define the content to share
var activityItems = new NSObject[] { uiImage };
UIActivity[] applicationActivities = null;
var activityController = new UIActivityViewController(activityItems, applicationActivities);
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone)
{
// Phone
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(activityController, true, null);
}
else
{
// Tablet
var popup = new UIPopoverController(activityController);
UIView view = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
CGRect rect = new CGRect(view.Frame.Width/2, view.Frame.Height, 50, 50);
popup.PresentFromRect(rect, view, UIPopoverArrowDirection.Any, true);
}
}
Don't know if from the memory management aspect it is a good idea to load the image at once. What will happen if the image is too big for holding it completely in RAM? See here for example.
Strings
var activityItems = new NSObject[] { UIActivity.FromObject(new NSString(text)) };
Only text.
NSUrl
NSUrl url = NSUrl.CreateFileUrl(filepath, false, null);
Here in most cases the same app appear. But for example the PDF reader doesn't appear for a PDF file. The preview in mail on the other side shows Adobe Acrobat.
Everything
var activityItems = new NSObject[] { NSData.FromFile(filepath) };
The last approach has the disadvantage that not all apps are displayed, which for example could open a PDF file. Also this applies.
I want to use all types of files. I don't think a subclass of UIActivity would help here. Perhaps a sublcass of UIActivityItemProvider?
Side note: You can also post your solutions in Objective C/Swift.
I tried to implement UIActivityItemProvider, but here again not all apps where shown for the corresponding filetype. E.g. for a docx-document Word was not shown.
Now I switched to UIDocumentInteractionController and now there are many apps available.
UIDocumentInteractionController documentController = new UIDocumentInteractionController();
documentController.Url = new NSUrl(filepath, false);
string fileExtension = Path.GetExtension(filepath).Substring(1);
string uti = UTType.CreatePreferredIdentifier(UTType.TagClassFilenameExtension.ToString(), fileExtension, null);
documentController.Uti = uti;
UIView presentingView = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
documentController.PresentOpenInMenu(CGRect.Empty, presentingView, true);
Imho there are too many apps, because the file type xml should not be really be supported by a PDF reader, but it is. Nevertheless, it seems to work now thanks to this post:
In general if you’re sharing an image or url, you might want to use a UIActivityViewController. If you’re sharing a document, you might want to use a UIDocumentInteractionController.
I'm having a problem when using the UIActivityViewController to share spreadsheets and videos (and probably other file types). The problem is that the right type identifiers are not found. For example, when I try to attach a spreadsheet, as seen in the code below:
let testFilePath = NSBundle.mainBundle().pathForResource("test_spreadsheet", ofType: "xlsx")
let testData = NSData(contentsOfFile: testFilePath)
let list = [testData]
let activityViewController = UIActivityViewController(activityItems: list, applicationActivities: nil)
navigationController.presentViewController(activityViewController, animated: true, completion: nil)
I get the following log message:
2014-08-20 16:45:58.035 TestExtensionPoints[12608:194174] Discovered extensions: {(
<NSExtension: 0x7fb7fca13680> {id = com.asperasoft.faspex-app.FaspexExtension}
)} for attributes: {
NSExtensionActivationRule = {
extensionItems = (
{
attachments = (
{
registeredTypeIdentifiers = (
"public.data"
);
}
);
}
);
};
NSExtensionPointName = (
"com.apple.share-services",
"com.apple.ui-services",
"com.apple.services"
);
}
Note that the registered type identifier is public.data, instead of public.spreadsheet.
This differs from when I try to attach, say, a pdf or a png file. If I just change the file to a pdf, as seen in the code below (only the first line changes):
let testFilePath = NSBundle.mainBundle().pathForResource("test_pdf", ofType: "pdf")
I get the following log message, which indeed DOES have the desired type identifier:
2014-08-20 16:50:52.973 TestExtensionPoints[12652:196132] Discovered extensions: {(
<NSExtension: 0x7ff2534285a0> {id = com.asperasoft.faspex-app.FaspexExtension}
)} for attributes: {
NSExtensionActivationRule = {
extensionItems = (
{
attachments = (
{
registeredTypeIdentifiers = (
"com.adobe.pdf"
);
}
);
}
);
};
NSExtensionPointName = (
"com.apple.share-services",
"com.apple.ui-services",
"com.apple.services"
);
}
Does anyone know why this is happening, and does anyone have a workaround? I know I can just write the files and attach URLs, but that's not really the purpose of this exercise.
Extra Background: I'm mainly asking this because I'm making a share extension, and am experimenting with how the data will be passed to me. I have already covered the case where it is passed as a URL and as a UIImage. These other cases would be fine, except for the fact that I am not getting the type identifiers I need.
The system probably sniffs for familiar types like PNG and PDF and not for esoteric types. Since PNG and PDF can be identified from the starting bytes, the activity system doesn't strictly need a UTI to identify them. However, the same may not be true of esoteric types like spreadsheets.
What you need is to implement UIActivityItemSource or subclass UIActivityItemProvider and provide these instances to the UIActivityViewController instead of naked NSData. This allows you to provide the UTI of the data explicitly to the UIActivityViewController.
For immediately available data, implement these UIActivityItemSource methods:
- activityViewControllerPlaceholderItem: to return the minimum data item that conforms to the type. You can probably pass an empty NSData here.
- activityViewController:itemForActivityType to return the actual data item.
- activityViewController:dataTypeIdentifierForActivityType: to return the UTI of the data item.
For data that can be lazily generated in another thread, implement these UIActivityItemProvider methods:
- item to return the actual data item. This will be called in a different thread, so your generation has to be thread safe.
- activityViewController:dataTypeIdentifierForActivityType: to return the UTI of the data item.
Now if the UTI is not a common UTI you'll also have to export them from your Info.plist.
Perhaps there's not a UTI for .xlsx files?
This question seemed to have a similar issue. The answer provided answers for .xlsx files, and more UTIs are listed here with instructions on implementation here.
I've tried sharing an NSData object of the file contents, and the activity view comes up with the mail option, and then the mail compose controller displays, but there's no attachment.
I've tried sharing an NSUrl with the path of the file, but in that case when the activity view comes up it takes up the whole screen but is blank except for the "cancel" button at the bottom. Weird. Also the activity view only comes up on the device in this case, it never even comes up on the simulator.
If I convert the NSData to an NSString, then it does work, but it just pastes the string into the body of the email. I don't want that, I want to attach a file.
I've used the debugger to verify that the NSData object has (the correct) data and that the NSUrl object has the right file path. No dice.
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var file = Path.Combine(documents, "file.txt");
NSData dataToShare = NSFileManager.DefaultManager.Contents(file);
UIActivityViewController activityViewController = new UIActivityViewController(new NSObject[] { dataToShare }, null); //Email comes up but data isn't attached
// OR
UIActivityViewController activityViewController = new UIActivityViewController(new NSObject[] { new NSUrl(file) }, null); //Activity view takes up the whole screen and is blank
// OR
UIActivityViewController activityViewController = new UIActivityViewController(new NSObject[] { (NSString)dataToShare.ToString() }, null); //Pastes string into email body
We're experiencing a strange problem with a UIImagePickerController. In our application users are able to fill out a series of forms and also attach images and videos within these forms.
We allow users to add multiple photos / videos either from the camera roll or to be captured at the time of filling the form out.
We're using the UIImagePickerController to do this. The problem occurs when 1 or 2 images / videos are taken with the camera.
Once 1 or 2 images / videos are captured when the camera screen is re-entered for a third time the image is static and doesn't update. The view is stuck at the last frame of whatever was captured last.
If the capture button is pressed then the image / video suddenly updates and has captured what the camera was pointing at. From then on the picker is good for another go behaving normally. Additionally selecting a picture / video from the camera roll appears to make everything behave again for another picture / video. Finally when the screen isn't responding and the user has selected to take a picture the view will shrink to a small rectangle within the view. The controller is being setup as follows:
private void SourceChosen(EventHandler<UIImagePickerMediaPickedEventArgs> captureEvent, int buttonIndex, string[] mediaTypes)
{
var picker = ConfigurePicker(mediaTypes, captureEvent);
if (CameraAvailable && buttonIndex == 0)
{
picker.SourceType = UIImagePickerControllerSourceType.Camera;
picker.CameraDevice = UIImagePickerControllerCameraDevice.Rear;
this.NavigationController.PresentViewController(picker, true, () => { });
}
if ((!CameraAvailable && buttonIndex == 0) || (CameraAvailable && buttonIndex == 1))
{
picker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
this.NavigationController.PresentViewController(picker, false, () => { });
}
}
private UIImagePickerController ConfigurePicker(string[] mediaTypes, EventHandler<UIImagePickerMediaPickedEventArgs> captureEvent)
{
var mediaPicker = new UIImagePickerController();
mediaPicker.FinishedPickingMedia += captureEvent;
mediaPicker.Canceled += (sender, args) => mediaPicker.DismissViewController(true, () => { });
mediaPicker.SetBarDefaults();
mediaPicker.MediaTypes = mediaTypes;
return mediaPicker;
}
An example of a captureEvent is as follows:
void PhotoChosen(object sender, UIImagePickerMediaPickedEventArgs e)
{
UIImage item = e.OriginalImage;
string fileName = string.Format("{0}.{1}", Guid.NewGuid(), "png");
string path = Path.Combine(IosConstants.UserPersonalFolder, fileName);
NSData imageData = item.AsPNG();
CopyData(imageData, path, fileName, ViewModel.Images, ((UIImagePickerController)sender));
}
private void CopyData(NSData imageData, string path, string fileName, List<AssociatedItem> collectionToAddTo, UIImagePickerController picker)
{
byte[] imageBytes = new byte[imageData.Length];
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, imageBytes, 0, Convert.ToInt32(imageData.Length));
File.WriteAllBytes(path, imageBytes);
AssociatedItem item = new AssociatedItem
{
StorageKey = fileName
};
collectionToAddTo.Add(item);
picker.DismissViewController(true, ReloadTables);
}
At the moment as you can see we're not holding a reference to the picker but we have tried variations of this code where we store a reference to the picker and dispose it after the CopyData method, we've added picker.Release(); after copydata and before the dispose (results in subsequent pickers crashing the application when displayed) and pretty much every other variation on the theme.
Does anyone have any idea why this might be occurring and how to fix it? It was my assumption that we might be running low on memory but neither disposing of it each time / only ever creating one instance and changing its mode from pictures to videos has any affect and we always see the same behaviour.
EDIT
Thanks to Kento and the below answer what we needed to get it all working as intended was something along the lines of:
public class PickerDelegate : UIImagePickerControllerDelegate
{
private readonly Action<UIImagePickerController, NSDictionary> _captureEvent;
public PickerDelegate(Action<UIImagePickerController, NSDictionary> captureEvent)
{
_captureEvent = captureEvent;
}
public override void FinishedPickingMedia(UIImagePickerController picker, NSDictionary info)
{
_captureEvent(picker, info);
}
}
Then to get an image
void PhotoChosen(UIImagePickerController picker, NSDictionary info)
{
UIImage item = (UIImage)info.ObjectForKey(UIImagePickerController.OriginalImage);
string fileName = string.Format("{0}.{1}", Guid.NewGuid(), "png");
string path = Path.Combine(IosConstants.UserPersonalFolder, fileName);
NSData imageData = item.AsPNG();
CopyData(imageData, path, fileName, ViewModel.Images, picker);
}
Or to get a video
void VideoChosen(UIImagePickerController picker, NSDictionary info)
{
var videoURL = (NSUrl)info.ObjectForKey(UIImagePickerController.MediaURL);
NSData videoData = NSData.FromUrl(videoURL);
string fileName = string.Format("{0}.{1}", Guid.NewGuid(), "mov");
string path = Path.Combine(IosConstants.UserPersonalFolder, fileName);
CopyData(videoData, path, fileName, ViewModel.Videos, picker);
}
I had this same problem.
The post here is not marked as the answer but it did solve it for me: https://stackoverflow.com/a/20035698/2514318
I'm guessing this is a bug w/ MonoTouch when using the FinishedPickingMedia event. I have read that there are leaks with using UIImagePickerController (regardless of using obj c or Mono) so I prefer to keep the instance around and re-use it. If you do re-create it each time, I would recommend disposing the previous instance.
Can anyone from Xamarin weigh in on if this is a bug or not?
This post helped me a lot, so I decided to make a very simples sample and post on github for anyone that may need it: https://github.com/GiusepeCasagrande/XamarinSimpleCameraSample
Intro - I am working on a iOS game using Flash CS5.5. In my game i am loading a image via the camera roll, and gonna use the image in the gameplay. I want the image to load back in when the user restarts the game.
Question - Is there a way to access such image again without having to open up the camera roll interface.
Can i use the flash.display.Loader class? Is there any special things i need to do or set? Or is there another way?
I've tried just saving the file, given from the MediaEvent dispatched when you select an image.
But i have no luck using the URL given in the loader object. I am using the example from this page as my base: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/CameraRoll.html.
Yes this is possible. You need to save the image into the apps documents directory using the File class with something like this: (this uses the Adobe JPGEncoder class)
f = File.documentsDirectory.resolvePath("logo.jpg");
stream = new FileStream();
stream.open(f, FileMode.WRITE);
j = new JPGEncoder(80);
var bytes:ByteArray = j.encode(visualLogo.bitmapData);
stream.writeBytes(bytes, 0, bytes.length);
stream.writeBytes(bytes, 0, bytes.bytesAvailable);
stream.close();
//I read the file back in to test that it has been successfully written
stream.openAsync(f, FileMode.READ);
stream.addEventListener(Event.COMPLETE, bringBoardOn, false, 0, true);
Then load it back in using something like this:
f = File.documentsDirectory.resolvePath ("logo.jpg");
if (f.exists == true) {
var _urlRequest:URLRequest = new URLRequest(f.url);
loader=new Loader;
loader.load(_urlRequest);
loader.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent):void{ trace(e) });
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, ImageLoaded, false, 0, true);
_urlRequest = null;
}