I have a UIDocumentInteractionController instance (that DOES have a strong reference in my class, I am aware of the memory issues about it) and I want to send a photo file to Instagram.
I saved the file using the ig extension (tried igo as well) and I am presenting the controller. Instagram is displayed on the list. I tap Instagram, and nothing happens.
NSURL *imageFile = [NSURL fileURLWithPath:path];
interactionController = [UIDocumentInteractionController interactionControllerWithURL:imageFile];
interactionController.UTI = #"com.instagram.photo";
interactionController.annotation = [NSDictionary dictionaryWithObject:#"my caption" forKey:#"InstagramCaption"];
interactionController.delegate = self;
[interactionController presentOpenInMenuFromRect:self.view.frame inView:self.view animated:YES];
To investigate further, I've set my calling class as a delegate and implemented the willBeginSendingToApplication: and didEndSendingToApplication: methods. Interestingly, I've realized that willBeginSendingToApplication: does get called, but didEndSendingToApplication: does not. I've tried changing my file extensions, changing UTI to com.instagram.exclusivegram, checking if the file URL is correct etc. but none of them seem to work. No error, nothing in the console or anything. The interaction controller closes, my app keeps working as it was working before, just nothing happens. I've read that there can be some issues on iOS 6, but my app is an iOS 6 app, so I can't test it on iOS < 6. The only thing that is close to my problem that I've found is UIDocumentInteractionController, No File Extension but UTI but it dives too much into the low level bits, nor I have a non-ARC code.
What could be the cause of the problem?
This can happen if the file doesn't exist, but also if you haven't constructed the file URL correctly. This plagued me for a while.
Make sure you construct your file URL like this:
NSURL *pagesURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"MyGreatPDF.pdf" ofType:nil]];
and not like this:
NSURL *pagesURL = [NSURL fileURLWithPath:#"MyGreatPDF.pdf"];
The latter still forms a valid URL, but it gets a "private" prefix, i.e. file:///private/var/mobile/Applications/00000000-0000-0000-0000-000000000000/MyGreatApp.app/MyGreatPDF.pdf rather than file:///var/mobile/Applications/00000000-0000-0000-0000-000000000000/MyGreatApp.app/MyGreatPDF.pdf
After a long while, I've found out that the file was not saved correctly, and didn't exist. iOS wasn't throwing out any sort of an error, failing silently. I've corrected the code about generating the file, and when the file was there, the controller appeared. Maybe Apple should add some assertion/exception mechanism for handling non-existent files in document interaction controller.
This may also caused by the file name's extension.
If the target app declare it only support file with png extension in Info.plist -> Exported Type UTIs -> Equivalent Types -> public.filename-extension, and you send a file with jpg extension, the target app won't open as well.
Related
We have an iOS application that manages documents via Core Data. The actual files reside in the app's shared container so that the app's file provider extension can also access them for Files.app support. We want to give the user the option to open these files in third-party apps so that they can edit them in-place instead of sending a copy to the other app.
We provide a UIActivityViewController for sharing files with other apps. We also provide a UIActivity that shows a UIDocumentInteractionController which seems to work better in some cases. We give the UIActivityViewController the document's file URL, the raw text content, and printable data.
This works but all third-party editors are shown as Copy to … instead of Open in …
We've also set the UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace properties to YES in the app's info.plist but they seem to be only relevant for open-in-place when sharing files residing in the app's Documents folder.
Now we've stumbled upon the NSItemProviderFileOptionOpenInPlace option for NSItemProvider. As we're already supporting a file provider extension and from Apple's documentation this seemed like a great place to accomplish just what we want.
Adding a "pure" NSItemProvider works, in a way, but shows fewer options than when also sharing the file URL and text in addition (which is expected). However, when we use -[NSItemProvider registerFileRepresentationForTypeIdentifier:fileOptions:visibility:loadHandler:] with the said option (or just zero, same result) and return the file URL in the loadHandler's completionHandler() nothing is shared anymore. E.g., Mail no longer attaches the file, Messages doesn't show the document for sending.
These are the relevant bits of the code:
NSMutableArray *items = [NSMutableArray array];
NSMutableArray <UIActivity *> *activities = [NSMutableArray array];
NSURL *fileURL = self.record.metadata.fileURL;
NSString *fileUTI = self.record.metadata.uti;
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem: fileURL typeIdentifier: fileUTI];
[itemProvider registerFileRepresentationForTypeIdentifier:fileUTI fileOptions:NSItemProviderFileOptionOpenInPlace visibility:YES loadHandler:^NSProgress * _Nullable(void (^ _Nonnull completionHandler)(NSURL * _Nullable, BOOL, NSError * _Nullable))
{
if (fileURL)
completionHandler(fileURL, YES, nil);
else
completionHandler(nil, YES, [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]);
return nil;
}];
[items addObject:itemProvider];
self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:activities];
[UIAppDelegate.splitViewController presentViewController:self.activityViewController animated:YES completion:nil];
The using the Share menu the item provider's load handler is correctly called and the file's actual URL returned.
Is that not how NSItemProviderFileOptionOpenInPlace is intended to be used? Or are we using it simply wrong? Apple's description is extremely sparse and we couldn't find any information elsewhere on the internet except for the official documentation.
I've found out what my problem was: Not deep enough understanding of the relationship between the activity view controller and file providers.
As all my files reside in the shared container and are published also through the file provider extension, what I need to share through the activity view controller is the exact same URL that is shared through the file provider extension. Technically then the app that opens the file accesses it through there file provider mechanism.
Posting after finding answer
After "rubber duck debugging" this answer a bunch, I finally came across the correct answer on a question that appears to me to be unrelated. I think this question (and its answer) are still relevant, so I'm posting the question and will post my own answer to hopefully help others like me.
I am creating a PDF in my iOS app that I would like to allow the user to export. For the purposes of this testing, I'm trying to save it to my personal Dropbox on a physical device.
I have turned on iTunes file sharing, and I can verify that the PDF file is being generated correctly, and when I copy it off of my device (iPad Pro Gen. 2 running iOS 11), I can open the PDF and it has the expected content and appearance.
I am able to get the document pop-up to display correctly, and I have options to share via:
Line 1: AirDrop
Line 2: Message, Mail, Add to Notes, (Facebook) Messenger, etc.
Line 3: Copy, Print, Save to Files, Save to Dropbox, etc.
No matter what I try to select (Save to Dropbox is the one I want to solve, but the issue seems universal), it fails. Of note, when I click Save to Dropbox, I do see the Dropbox panel display, but there is immediately a modal over top of the Save to Dropbox modal that says, "An unknown error occurred."
I have tried to look around and see how to get more information about this error, but I'm stumped. I'm not sure if it's correlated, but I get this message in the console:
[AXRun-PID] Client requesting unsuspension of PID:813 Name:<redacted>
Trying to google that error has proved unfruitful.
Here's the code where I generate the PDF and show the menu:
#pragma mark • Sharing Methods
- (void)showShareMenu {
NSArray *bookList = [BookManager bookList];
NSURL *pdfUrl = [PdfGenerator generatePdfFromBooks:bookList];
UIDocumentInteractionController *vc = [[UIDocumentInteractionController alloc] init];
vc.name = #"Booklet.pdf";
vc.URL = pdfUrl;
vc.UTI = #"com.adobe.pdf";
[vc presentOptionsMenuFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
}
I've tried using UIDocumentInteractionController *vc = [UIDocumentInteractionController interactionControllerWithURL:pdfUrl]; instead of the one above, but the results are the same.
I tried making self the delegate of vc and then tried to implement the following methods:
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
willBeginSendingToApplication:(nullable NSString *)application;
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
didEndSendingToApplication:(nullable NSString *)application;
Neither of those methods ever fired.
Interestingly, though I think I've supplied the file name correctly based on what I've read, the name in the File textbook in the Save to Dropbox modal is a current timestamp (e.g., File Oct 28, 11 12 22 PM). The Dropbox modal stays up until I click "OK" on the "An unknown error occurred" modal, and then disappears immediately.
It seems like I'm somehow not providing the right information, but I'm not sure how. It seems like there ought to be a delegate method to indicate an error to me, but I don't see anything like that in the docs. (It is late, and I have been looking at this for hours, including reading several related tutorials, so I could have missed something obvious.)
I came across this answer as an example question while asking this current question.
It doesn't really ask the same question I have, nor did that user have the same error outputs I did. But, the linked answer did work for me, too.
The problem I had in the code above was that I was not keeping the UIDocumentInteractionController around after I created it. Adding a private property fixed this issue. So, the following code now works:
#pragma mark • Sharing Methods
- (void)showShareMenu {
NSArray *bookList = [BookManager bookList];
NSURL *pdfUrl = [PdfGenerator generatePdfFromBooks:bookList];
self.docController = [UIDocumentInteractionController interactionControllerWithURL:pdfUrl];
self.docController.name = #"Booklet.pdf";
self.docController.UTI = #"com.adobe.pdf";
[self.docController presentOptionsMenuFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
}
I have an ios app built with Cordova.
I would like to open a web ressource file (.html) after a deep linking, in a UIWebView with the handleURL ios method.
I know I'm supposed to do this within my cordova JS files, but I would like to know the way to do it natively?
Let's say I define a my-app URL scheme and want to open file1.html.
What are the ways for doing that?
I have found this:
// Load the html as a string from the file system
NSString *path = [[NSBundle mainBundle] pathForResource:#"index" ofType:#"html"];
NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
// Tell the web view to load it
[WebView loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]];
But nothing happens, like it can't find that file.
Why is that?
put all files in a folder and while adding drag drop in project. and select create folder references and copy items if needed. and try to load in UIWebView
You make a new class that inherits from NSURLProtocol.
You need to call [NSURLProtocol registerClass:YOURCLASS] in your app
In your class, implement + (BOOL) canInitWithRequest:(NSURLRequest *) to return true if the scheme matches your scheme (e.g. my-app)
See the docs for NSURLProtocol to see how to implement the rest of the functionality. For simple file system reads, you can implement startLoading and call self.client callbacks (e.g. URLProtocol:didLoadData:). For more complex things, you might need to override more.
See this tutorial: http://www.raywenderlich.com/59982/nsurlprotocol-tutorial
I would like to provide the ability for users to tap the Action button and up pops the usual share sheet, which should include other apps to the right of the Messages, Facebook, etc icons - applications that can work with .txt files, or just an NSString.
I am currently displaying a Share sheet via UIActivityViewController, which is working great but it does not include other apps in the list. From reading other SO questions I concluded it's only possible to get those other apps to appear if you use UIDocumentInteractionController instead. I looked into creating a .txt file in a temp directory to share that file (instead of just sharing an NSString), but only Mail (no Copy) shows up when I tap the Share button. [Do note that if I run it on a real device not the simulator more apps other than Mail will appear and AirDrop too.] When I tap Mail, the app crashes: Unable to get data for URL: The operation couldn’t be completed. (Cocoa error 260.) Something is wrong with the way I'm creating/retrieving the .txt file.
My questions are:
Why is my code resulting in a crash when attempting to share the .txt file?
How can I get the Copy option to appear in the same Share sheet as the one that includes other apps?
To summarize: I need a share sheet that includes: Copy, AirDrop, Messages, Mail, Facebook, Twitter, Pages, Dropbox, etc for a simple string of text. Thanks!
The following lines of code lie inside my IBAction share button tap function:
UIActivityViewController approach:
UIActivityViewController *activityView = [[UIActivityViewController alloc] initWithActivityItems:#[self.myUITextField.text] applicationActivities:nil];
[self presentViewController:activityView animated:YES completion:nil];
Result:
UIDocumentInteractionController approach:
NSString *fileName = [NSString stringWithFormat:#"%#mytextfile.txt", NSTemporaryDirectory()];
[self.myUITextField.text writeToFile:fileName
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
NSURL *textFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:#"mytextfile.txt"]];
UIDocumentInteractionController *documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:textFileURL];
[documentInteractionController presentOptionsMenuFromBarButtonItem:sender animated:YES];
Result (will show more apps and AirDrop if I run on a real device):
Example of what I want to obtain - minus the 3 extra options at the bottom:
If I cannot obtain the above screenshot with a string (instead of a photo) for some reason, I am willing to implement it how Dropbox has done it. They added an Open In button at the bottom that presents a different sheet that only shows additional apps. Note that I would still need a Copy option on the original sheet.
Question 1: Why is my code resulting in a crash
Cocoa error 260 is an NSFileReadNoSuchFileError according to the Foundation Constants Reference document. Looking at your code, the only way how I can see that file creation might fail is if self.myUITextField is nil.
I suggest that you check this first. If the property is not nil, then check whether writeToFile:atomically:encoding:error: returns an error.
Question 2: How can I get the Copy option to appear
First, assign a delegate to the controller:
documentInteractionController.delegate = self;
Then implement the following two delegate methods:
- (BOOL) documentInteractionController:(UIDocumentInteractionController*)controller canPerformAction:(SEL)action
{
if (#selector(copy:) == action)
return YES;
else
return NO;
}
- (BOOL) documentInteractionController:(UIDocumentInteractionController*)controller performAction:(SEL)action
{
if (#selector(copy:) != action)
return NO;
// Perform the copy: action
return YES;
}
Both methods are marked deprecated since iOS 6, but they still seem to work in iOS 7. Unfortunately, I have no idea how to implement the copy: action without those two methods - and neither does Apple, or so it seems to me, since they do not offer a replacement, and the official Document Interaction Programming Topics for iOS document still happily refers to the methods without indication that they are deprecated.
Anyway, here's a simple but complete implementation of the second delegate method:
- (BOOL) documentInteractionController:(UIDocumentInteractionController*)controller performAction:(SEL)action
{
if (#selector(copy:) != action)
return NO;
NSStringEncoding usedEncoding;
NSError* error;
NSString* fileContent = [NSString stringWithContentsOfURL:controller.URL
usedEncoding:&usedEncoding
error:&error];
UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setString:fileContent];
return YES;
}
I have downloaded some files from server and stored into local file system. I want to open them with default application in the device. How can I open files with default application. Please provide some sample code.
thanks
First you need to represent the resource (downloaded file to be opened) with an NSURL object.
The following assumes an NSString named filePath that is already initialised with the path to the resource to open.
NSURL *resourceToOpen = [NSURL fileURLWithPath:filePath];
Then it's best to check first that there is an app that will open the resource.
BOOL canOpenResource = [[UIApplication sharedApplication] canOpenURL:resourceToOpen];
Finally if the above line returns yes then open the resource.
if (canOpenResource) { [[UIApplication sharedApplication] openURL:resourceToOpen]; }
I quote the following from UIApplication class reference with respect to the instance method canOpenURL:
This method guarantees that that if openURL: is called, another app will be launched to handle it. It does not guarantee that the full URL is valid.
However, if your wish to present the user with a list of apps that have registered with the appropriate UTI for that file type you can do something like this-
UIDocumentInteractionController *documentController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
documentController.delegate = self;
[documentController presentOpenInMenuFromRect:CGRectZero inView:self.view animated:YES];
You must implement the UIDocumentInteractionControllerDelegate protocol. Also for known file types the system should resolve the correct assignment of the UTI property without setting it.