iPhone SDK: Open file in my app [duplicate] - iphone

On the subject of associating your iPhone app with file types.
In this informative question I learned that apps could be associated with custom URL protocols.
That was almost one year ago and since then Apple introduced 'Document Support' which goes a step further and allows apps to associate with file types. There is a lot of talk in the documentation about how to set up your app to launch other appropriate apps when it encounters an unknown file type. This means the association doesn't work out of the box for any app, like the URL protocol registering did.
This leads me to the question: have system apps like Safari or Mail implemented this system for choosing associated applications, or will they do nothing, as before?

File type handling is new with iPhone OS 3.2, and is different than the already-existing custom URL schemes. You can register your application to handle particular document types, and any application that uses a document controller can hand off processing of these documents to your own application.
For example, my application Molecules (for which the source code is available) handles the .pdb and .pdb.gz file types, if received via email or in another supported application.
To register support, you will need to have something like the following in your Info.plist:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array>
<string>Document-molecules-320.png</string>
<string>Document-molecules-64.png</string>
</array>
<key>CFBundleTypeName</key>
<string>Molecules Structure File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.sunsetlakesoftware.molecules.pdb</string>
<string>org.gnu.gnu-zip-archive</string>
</array>
</dict>
</array>
Two images are provided that will be used as icons for the supported types in Mail and other applications capable of showing documents. The LSItemContentTypes key lets you provide an array of Uniform Type Identifiers (UTIs) that your application can open. For a list of system-defined UTIs, see Apple's Uniform Type Identifiers Reference. Even more detail on UTIs can be found in Apple's Uniform Type Identifiers Overview. Those guides reside in the Mac developer center, because this capability has been ported across from the Mac.
One of the UTIs used in the above example was system-defined, but the other was an application-specific UTI. The application-specific UTI will need to be exported so that other applications on the system can be made aware of it. To do this, you would add a section to your Info.plist like the following:
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.plain-text</string>
<string>public.text</string>
</array>
<key>UTTypeDescription</key>
<string>Molecules Structure File</string>
<key>UTTypeIdentifier</key>
<string>com.sunsetlakesoftware.molecules.pdb</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<string>pdb</string>
<key>public.mime-type</key>
<string>chemical/x-pdb</string>
</dict>
</dict>
</array>
This particular example exports the com.sunsetlakesoftware.molecules.pdb UTI with the .pdb file extension, corresponding to the MIME type chemical/x-pdb.
With this in place, your application will be able to handle documents attached to emails or from other applications on the system. In Mail, you can tap-and-hold to bring up a list of applications that can open a particular attachment.
When the attachment is opened, your application will be started and you will need to handle the processing of this file in your -application:didFinishLaunchingWithOptions: application delegate method. It appears that files loaded in this manner from Mail are copied into your application's Documents directory under a subdirectory corresponding to what email box they arrived in. You can get the URL for this file within the application delegate method using code like the following:
NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
Note that this is the same approach we used for handling custom URL schemes. You can separate the file URLs from others by using code like the following:
if ([url isFileURL])
{
// Handle file being passed in
}
else
{
// Handle custom URL scheme
}

In addition to Brad's excellent answer, I have found out that (on iOS 4.2.1 at least) when opening custom files from the Mail app, your app is not fired or notified if the attachment has been opened before. The "open with…" popup appears, but just does nothing.
This seems to be fixed by (re)moving the file from the Inbox directory. A safe approach seems to be to both (re)move the file as it is opened (in -(BOOL)application:openURL:sourceApplication:annotation:) as well as going through the Documents/Inbox directory, removing all items, e.g. in applicationDidBecomeActive:. That last catch-all may be needed to get the app in a clean state again, in case a previous import causes a crash or is interrupted.

BIG WARNING: Make ONE HUNDRED PERCENT sure that your extension is not already tied to some mime type.
We used the extension '.icz' for our custom files for, basically, ever, and Safari just never would let you open them saying "Safari cannot open this file." no matter what we did or tried with the UT stuff above.
Eventually I realized that there are some UT* C functions you can use to explore various things, and while .icz gives the right answer (our app):
In app did load at top, just do this...
NSString * UTI = (NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
(CFStringRef)#"icz",
NULL);
CFURLRef ur =UTTypeCopyDeclaringBundleURL(UTI);
and put break after that line and see what UTI and ur are -- in our case, it was our identifier as we wanted), and the bundle url (ur) was pointing to our app's folder.
But the MIME type that Dropbox gives us back for our link, which you can check by doing e.g.
$ curl -D headers THEURLGOESHERE > /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 27393 100 27393 0 0 24983 0 0:00:01 0:00:01 --:--:-- 28926
$ cat headers
HTTP/1.1 200 OK
accept-ranges: bytes
cache-control: max-age=0
content-disposition: attachment; filename="123.icz"
Content-Type: text/calendar
Date: Fri, 24 May 2013 17:41:28 GMT
etag: 872926d
pragma: public
Server: nginx
x-dropbox-request-id: 13bd327248d90fde
X-RequestId: bf9adc56934eff0bfb68a01d526eba1f
x-server-response-time: 379
Content-Length: 27393
Connection: keep-alive
The Content-Type is what we want. Dropbox claims this is a text/calendar entry. Great. But in my case, I've ALREADY TRIED PUTTING text/calendar into my app's mime types, and it still doesn't work. Instead, when I try to get the UTI and bundle url for the text/calendar mimetype,
NSString * UTI = (NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType,
(CFStringRef)#"text/calendar",
NULL);
CFURLRef ur =UTTypeCopyDeclaringBundleURL(UTI);
I see "com.apple.ical.ics" as the UTI and ".../MobileCoreTypes.bundle/" as the bundle URL. Not our app, but Apple. So I try putting com.apple.ical.ics into the LSItemContentTypes alongside my own, and into UTConformsTo in the export, but no go.
So basically, if Apple thinks they want to at some point handle some form of file type (that could be created 10 years after your app is live, mind you), you will have to change extension cause they'll simply not let you handle the file type.

To deal with any type of files for my own APP, I use this configuration for CFBundleDocumentTypes:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>IPA</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
<string>public.content</string>
<string>public.data</string>
<string>public.database</string>
<string>public.composite-content</string>
<string>public.contact</string>
<string>public.archive</string>
<string>public.url-name</string>
<string>public.text</string>
<string>public.plain-text</string>
<string>public.source-code</string>
<string>public.executable</string>
<string>public.script</string>
<string>public.shell-script</string>
<string>public.xml</string>
<string>public.symlink</string>
<string>org.gnu.gnu-zip-archve</string>
<string>org.gnu.gnu-tar-archive</string>
<string>public.image</string>
<string>public.movie</string>
<string>public.audiovisual-​content</string>
<string>public.audio</string>
<string>public.directory</string>
<string>public.folder</string>
<string>com.apple.bundle</string>
<string>com.apple.package</string>
<string>com.apple.plugin</string>
<string>com.apple.application-​bundle</string>
<string>com.pkware.zip-archive</string>
<string>public.filename-extension</string>
<string>public.mime-type</string>
<string>com.apple.ostype</string>
<string>com.apple.nspboard-typ</string>
<string>com.adobe.pdf</string>
<string>com.adobe.postscript</string>
<string>com.adobe.encapsulated-​postscript</string>
<string>com.adobe.photoshop-​image</string>
<string>com.adobe.illustrator.ai-​image</string>
<string>com.compuserve.gif</string>
<string>com.microsoft.word.doc</string>
<string>com.microsoft.excel.xls</string>
<string>com.microsoft.powerpoint.​ppt</string>
<string>com.microsoft.waveform-​audio</string>
<string>com.microsoft.advanced-​systems-format</string>
<string>com.microsoft.advanced-​stream-redirector</string>
<string>com.microsoft.windows-​media-wmv</string>
<string>com.microsoft.windows-​media-wmp</string>
<string>com.microsoft.windows-​media-wma</string>
<string>com.apple.keynote.key</string>
<string>com.apple.keynote.kth</string>
<string>com.truevision.tga-image</string>
</array>
<key>CFBundleTypeIconFiles</key>
<array>
<string>Icon-76#2x</string>
</array>
</dict>
</array>

Install duti. It's the most complete and serious solution to instantly bind UTIs to Appls. You have it here: duti on GitHub
If you prefer something with Graphic Interface: SwiftDefaultApps on GitHub

Related

I can write iCloud Documents, but cannot see them

A am working with UIDocuments and iCloud. I am successfully able to write out a file to iCloud but not seeing in on the device and the files app.
I have the following in info.plist.
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.myAppName</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>myAppName</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>
Inside my code I can create the directory and then write the file.
var containerUrl: URL? {
return FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("myAppName")
}
let url = containerUrl?.appendingPathComponent("fileName.fileType")
print("url: \(url)")
The print returns:
file:///private/var/mobile/Library/Mobile%20Documents/iCloud~myAppName/myAppName/fileName.fileType
I can test and see that the files are written out with code such as:
let boolTest: Bool = !FileManager.default.fileExists(atPath: testUrl!.path, isDirectory: nil)
print("!file exist: \(boolTest)")
In the "Signing & Capabilities" tab of my Target under iCloud I have clicked "iCloud Documents" and have a container called iCloud.myAppName that is assigned to my Team. (Note: There are two other checks "Key-value storage" and "CloudKit". I have tried those both ways. I believe they should be unchecked.)
Regardless, all seems to work well. I can see through the fileExist that it is written. However, I cannot see the documents in the File app or pull up the documents through the app. Only the documents on the device.
At this point, I am not sure what I am missing. Nor do I have any ideas as to how to debug this.
Save them within the Documents folder in cloud container. No need to add .appendingPathComponent("myAppName")

how to repeat or loop an applescript

I was looking for an option to color all unread mails in apple mail and I found the following script
tell application "Mail"
set background color of (messages of inbox whose read status is false) to red --unread
set background color of (messages of inbox whose read status is true) to none --default read
end tell
I added this script to a mail rule, which applies to every message, then it works almost perfect. The only problem is that it works only for one time. So if I get a new message, than the script doesn't notice it (so it is not red, only bold), or if I read an email, then it will be still red.
Please can someone help me, that the script will run in loop, or that the script will run each second.
Just for information, I´m an windows user (mac is really new for me) and I was looking for an scheduled task option, but I didn't found such easy thing on my mac.
You have several options to run scheduled tasks. Apple suggests to use the "launchd" agent. Just google "launchd tutorial" for many examples.
The basics are this... you create a plist file (an xml text file with the .plist file extension) with whatever instructions you want and put that it in the folder ~/Library/LaunchAgents. The tutorials will show you how to create many different kinds of plist files. Once you add your plist to that folder then logout/login to make it take effect. To disable a plist fie from running then remove it from the folder and logout/login. It's that simple.
As an example plist for your specific request, this one will run your applescript every 60 seconds...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myName.myProgramName</string>
<key>ProgramArguments</key>
<array>
<string>osascript</string>
<string>/path/to/applescript.scpt</string>
</array>
<key>StartInterval</key>
<integer>60</integer>
</dict>
</plist>
So in that plist example you just need to put your values in "com.myName.myProgramName", "/path/to/applescript.scpt", and "60" which is the number of seconds to run the applescript.
NOTE: you should name your plist file the same name as you give for "com.myName.myProgramName" with ".plist" as its file extension.
Good luck.

Importing text files into an iOS App

I noticed that when saving a file from within an iOS App using:
writeToFile:myFile atomically:YES
the resulting file is actually saved in XML format, and always has the following code:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<string>Sentence 1</string>
<string>Sentence 2</string>
<string>Sentence 3</string>
<string>Sentence 4</string>
</array>
</plist>
So its always in an XML/Plist format.
But if I try to import into the App a text file which was NOT created from within an iOS App - it doesn't seem to know how to recognize it.
For example, if I take a plain text file created in TextEdit and drag it into my App's Documents folder and then run the App again, the App sees this new file, but when I try to load its contents using:
initWithContentsOfFile:myFile
the result I get is: (null)
Is this because the default format of files created in TextEdit is ".rtf"? Or should I be using an altogether different method to read plain-text files into the App other than
initWithContentsOfFile:myFile?
Ultimately I'd like for the App to be able to load existing text files - maybe even Microsoft WORD files - and work with them. I'd like for the user to drag their existing files into the App's folder in iTunes, and then sync their device with iTunes - which will then copy these new files onto the device and when they run their App again, the app will see the new files and be able to open them and read their contents.
Any suggestions?
You've mentioned the writeToFile:atomically: method but you haven't told us what class this method is being called on. For example, NSString, NSArray, NSDictionary and other classes have a method with this name and they all behave differently as might expect given teh different types of data structures.
From the above file output, it looks like you're probably calling this on an NSArray which is why it gets written in Plist format. I assure you that strings get written plainly if you using an NSString, eg:
NSString *s = #"A string";
[s writeToFile:path atomically:YES];
will produce a file with:
A String
You may want to convert various other data structures into a string before you write it to a file.

Cannot open docx file through webbrowser in app using OpenIn functionality

I am using the "OpenIn" functionality and opening files from web browser into my application. All the file types are registered and they work fine. When tried to open docx, pptx or other file from web browser, the "Open In" box did not show my application name. Though I can view a docx or pptx file within my application by using UIDocumentInteractionController. I dont know where the problem lies as its working very fine.
Any suggestions are welcome.
Thanks for help in advance
doc UTI:com.microsoft.word.doc
docx UTI:org.openxmlformats.wordprocessingml.document
ppt UTI:com.microsoft.powerpoint.ppt
pptx UTI:org.openxmlformats.presentationml.presentation
xls UTI:com.microsoft.excel.xls
xlsx UTI:org.openxmlformats.spreadsheetml.sheet
Sample:
<dict>
<key>CFBundleTypeName</key>
<string>Microsoft Word 2003 XML document</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>org.openxmlformats.wordprocessingml.document</string>
</array>
</dict>
In your app's info.plist, you would have added something similar to the following, to allow your app to open, say a PDF file, right?
Just replace the above with this,
Notice here, that the Document Content Type UTIs has been changed to public.item, which is what UTIs such as ppt, doc etc. conform to.
Hence now, you should be able to get the "Open in.." option for pdf, doc, docx, ppt, pptx, xls, xlsx.
UIWebView does not support the file formats of MS Office 2007 onwards i.e. docx,pptx,xlsx because of the file architecture.

XCode reports localized plist is corrupt

I have a plist file that I used in my app that I can localize so I get two entries in my project, one for English and one for Spanish and when I compile an run the app it works but of course at this stage the contents are identical.
I then in Finder replace the Spanish plist with one that has been translated for me into Spanish and I can in the XCode editor view the content without problem.
However when I try to compile I get an error stating:
.../en.lproj/myData.plist:0: error: reading plist: The data couldn’t be read because it has been corrupted.
But the English one has not been touched?
Surely you can copy a localized file into the project in this manner?
open disk utility repair permissions.
open terminal and run this command:
plutil -s /somewhere/yourfile1.plist
It will make you focus with all details of the problem by showing you the exact error and line. So you'll have to go the reported line & fix it by yourself with a text editor.
One thing that can happen is someone messed up one of your tags.
In Xcode right click on your Spanish plist, select Open AS then Source Code
Then check your plist to ensure all of your opening and closing tags are still there, there are no typos, are there any garbage characters in there, and that you are not trying to use a string in an integer etc:
Example:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>1</key>
<dict>
<key>a</key>
<string>a1</string>
<key>b</key>
<string>b1</string>
</dict>
<key>2</key>
<dict>
<key>a</key>
<string>a2</string>
<key>b</key>
<string>b2</string>
</dict>
<key>3</key>
<dict>
<key>a</key>
<string>a3</string>
<key>b</key>
<string>b3</string>
</dict>
</dict>
</plist>
I once had some problems when something was copied and pasted into Xcode from a PDF (some kind of incorrect symbol maybe?) and it worked fine when I just re-entered it. Also its often difficult to generate a valid plist from a word processor.
That can also happen with strings file if you forget the semicolons at the end of the lines. Silly me.
This can also happen if you have an & within a string.
Replace:
<string>SomeText & SomeMoreText</string>
With:
<string>SomeText & SomeMoreText </string>
For benefit of anyone who had my exact problem:
I had the corrupt plist problem too.
My plist had been generated by exporting a spreadsheet in OpenOffice to CSV (in UTF-8 format). Then I'd convert the CSV file to a plist using a simple Python script I'd written. I believe my problem was that there were some cell reference errors in the spreadsheet I saved (i.e. the cells said "Err:511"). The existence of errors was causing the UTF-8 export to fail somehow, hence the corrupt plist.
I have not absolutely verified the above was the case, but I'm pretty sure that was the problem: after I fixed the errors in some cells in OpenOffice, my plist works again.
This answer demonstrates the use of the iconv command to verify a file is correctly encoded -- could be useful in cases like this.
This happened to me because I managed to drop a random quotation mark (") in my plist. Removed it and got it to work.
I opened the file in TextWrangler all used Cmd-F to search for any characters that might be causing the offence.
For me it was a space in the name of localized string:
Instead of this_is_the_string_we_will_translate
I had this_is_the string_we_will_translate.
Tricky one to spot.
A great place to start debugging plists is to use an XML validator such as: http://www.w3schools.com/xml/xml_validator.asp
This feature is also built into most web development environments.
Happened to me, missed 0:
2012-12-9T06:00:00Z
should be
2012-12-09T06:00:00Z