支持在我的 iOS 邮件和 Safari 应用程序中打开...菜单项
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7942597/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Supporting Open In... menu item in my app for iOS Mail And Safari
提问by CodaFi
I need to have my app open documents from the Safari and Mail apps with that "Open In..." thing in the UIDocumentInteractionController
class. How do I accomplish this?
我需要让我的应用程序从 Safari 和邮件应用程序中打开文档,并在UIDocumentInteractionController
课堂上使用“打开方式...” 。我该如何实现?
回答by CodaFi
I know this was extremely frustrating for me as a beginning programmer, or even as a moderately skilled one now. File I/O through the Mail and Safari apps involves very... interestingly named conventions within the app itself. So let's get our hands dirty with an Xcode project for iPhone. Open Xcode (I will be using 4.2 for this Tutorial) and select the 'Single View' application template (or create an empty project, then add a single view with a .xib).
我知道这对我作为一个初级程序员,或者现在作为一个中等技能的程序员来说是非常令人沮丧的。通过 Mail 和 Safari 应用程序进行的文件 I/O 涉及非常……有趣的应用程序本身命名约定。因此,让我们动手做一个 iPhone 的 Xcode 项目。打开 Xcode(我将在本教程中使用 4.2)并选择“单一视图”应用程序模板(或创建一个空项目,然后添加一个带有 .xib 的单一视图)。
In that newly created application, rename the view controller (and associated xib) to OfflineReaderViewController
, and then we'll get down to the code. (We will touch every file but the prefix header and main.m, so be aware that you'll need everything in front of you!)
在那个新创建的应用程序中,将视图控制器(和相关联的 xib)重命名为OfflineReaderViewController
,然后我们将进入代码。(我们将触及除前缀头文件和 main.m 之外的每个文件,因此请注意,您将需要前面的所有文件!)
Enter the AppDelegate header and paste the following code into it:
输入 AppDelegate 标头并将以下代码粘贴到其中:
#import <UIKit/UIKit.h>
@class OfflineReaderViewController;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) OfflineReaderViewController *viewController;
@end
Then enter the Delegate's .m file and paste the following code in verbatim:
然后输入 Delegate 的 .m 文件并逐字粘贴以下代码:
#import "AppDelegate.h"
#import "OfflineReaderViewController.h"
@implementation AppDelegate
@synthesize window;
@synthesize viewController;
-(BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
// Make sure url indicates a file (as opposed to, e.g., http://)
if (url != nil && [url isFileURL]) {
// Tell our OfflineReaderViewController to process the URL
[self.viewController handleDocumentOpenURL:url];
}
// Indicate that we have successfully opened the URL
return YES;
}
- (void)dealloc
{
[window release];
[viewController release];
[super dealloc];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.viewController = [[[OfflineReaderViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
/*
Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
*/
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
/*
Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
*/
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
}
- (void)applicationWillTerminate:(UIApplication *)application
{
/*
Called when the application is about to terminate.
Save data if appropriate.
See also applicationDidEnterBackground:.
*/
}
@end
This:
这个:
-(BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
if (url != nil && [url isFileURL]) {
[self.viewController handleDocumentOpenURL:url];
}
return YES;
}
Is the singular most important part of this tutorial. To break it down into its respective parts: -(BOOL)application:(UIApplication *)application
is our sample app; openURL:(NSURL *)url
is the URL that's sent to tell us what to open; sourceApplication:(NSString *)sourceApplication
is the application that sent the link; and annotation:(id)annotation
is an extra feature we won't get into.
是本教程中最重要的部分。将其分解为各自的部分:-(BOOL)application:(UIApplication *)application
是我们的示例应用程序;openURL:(NSURL *)url
是发送给我们以告诉我们要打开什么的 URL;sourceApplication:(NSString *)sourceApplication
是发送链接的应用程序;并且annotation:(id)annotation
是我们不会涉及的额外功能。
Now, we must layout our xib. Enter the xib (which should be entitled 'OfflineReaderViewController', but it doesn't matter with a xib, unless we call initWithNibName:
(which we won't)), and make it look like the picture below:
现在,我们必须布置我们的 xib。输入 xib(它应该被命名为“OfflineReaderViewController”,但它与 xib 无关,除非我们调用initWithNibName:
(我们不会调用)),并使它看起来像下图:
It is VERY important that you go into the UIWebView
's Attributes and check "Scales Pages To Fit", as this let's us zoom in and out on web pages with pinches. Don't worry about the connections just yet, we will be creating those shortly.
进入UIWebView
的属性并选中“缩放页面以适合”非常重要,因为这让我们可以用捏来放大和缩小网页。暂时不要担心连接,我们很快就会创建它们。
Enter the OfflineReaderViewController
header and paste in the following:
输入OfflineReaderViewController
标题并粘贴以下内容:
#import <UIKit/UIKit.h>
@interface OfflineReaderViewController : UIViewController
<UIDocumentInteractionControllerDelegate> {
IBOutlet UIWebView *webView;
}
-(void)openDocumentIn;
-(void)handleDocumentOpenURL:(NSURL *)url;
-(void)displayAlert:(NSString *) str;
-(void)loadFileFromDocumentsFolder:(NSString *) filename;
-(void)listFilesFromDocumentsFolder;
- (IBAction) btnDisplayFiles;
@end
Now the .m:
现在.m:
#import "OfflineReaderViewController.h"
@implementation OfflineReaderViewController
UIDocumentInteractionController *documentController;
-(void)openDocumentIn {
NSString * filePath =
[[NSBundle mainBundle]
pathForResource:@"Minore" ofType:@"pdf"];
documentController =
[UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
documentController.delegate = self;
[documentController retain];
documentController.UTI = @"com.adobe.pdf";
[documentController presentOpenInMenuFromRect:CGRectZero
inView:self.view
animated:YES];
}
-(void)documentInteractionController:(UIDocumentInteractionController *)controller
willBeginSendingToApplication:(NSString *)application {
}
-(void)documentInteractionController:(UIDocumentInteractionController *)controller
didEndSendingToApplication:(NSString *)application {
}
-(void)documentInteractionControllerDidDismissOpenInMenu:
(UIDocumentInteractionController *)controller {
}
-(void) displayAlert:(NSString *) str {
UIAlertView *alert =
[[UIAlertView alloc] initWithTitle:@"Alert"
message:str
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
- (void)handleDocumentOpenURL:(NSURL *)url {
[self displayAlert:[url absoluteString]];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[webView setUserInteractionEnabled:YES];
[webView loadRequest:requestObj];
}
-(void)loadFileFromDocumentsFolder:(NSString *) filename {
//---get the path of the Documents folder---
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory
stringByAppendingPathComponent:filename];
NSURL *fileUrl = [NSURL fileURLWithPath:filePath];
[self handleDocumentOpenURL:fileUrl];
}
-(void)listFilesFromDocumentsFolder {
//---get the path of the Documents folder---
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *fileList =
[manager contentsOfDirectoryAtPath:documentsDirectory error:nil];
NSMutableString *filesStr =
[NSMutableString stringWithString:@"Files in Documents folder \n"];
for (NSString *s in fileList){
[filesStr appendFormat:@"%@ \n", s];
}
[self displayAlert:filesStr];
[self loadFileFromDocumentsFolder:@"0470918020.pdf"];
}
- (IBAction) btnDisplayFiles {
[self listFilesFromDocumentsFolder];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[self openDocumentIn];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
@end
Those of you who are actively watching and not just copying everything I tell you to (just kidding) will know that this line: [[NSBundle mainBundle] pathForResource:@"Minore" ofType:@"pdf"];
will give us a SIGABRT because, well, the file doesn't exist! So, drag in any generic PDF that you've pulled from wherever (I recommend herebecause who doesnt spend their free time reading massive amounts of documentation?), then copy its title and paste it in with the suffix (.pdf) removed; the ofType:@"pdf"
part takes care of that for us. The line should look like this when you're done with it: [[NSBundle mainBundle] pathForResource:@"//file name//" ofType:@"pdf"];
那些积极观看而不只是复制我告诉你的所有内容(开玩笑)的人会知道这一行:[[NSBundle mainBundle] pathForResource:@"Minore" ofType:@"pdf"];
会给我们一个 SIGABRT 因为,好吧,该文件不存在!因此,将您从任何地方提取的任何通用 PDF 拖入(我在这里推荐,因为谁不花空闲时间阅读大量文档?),然后复制其标题并将其粘贴并删除后缀 (.pdf);这部ofType:@"pdf"
分会为我们处理这些。完成后,该行应如下所示:[[NSBundle mainBundle] pathForResource:@"//file name//" ofType:@"pdf"];
Now go back into the xib and hook up those IBOutlets
! All told, here's what your "File's owner" tab should look like:
现在回到xib并连接那些IBOutlets
!总而言之,您的“文件所有者”选项卡应该如下所示:
It seems we're done...but wait! We didn't do anything to get an "Open In..." menu up and running! Well, it turns out that there is some mucking around in the .plist file necessary. Open up the app .plist (with a quick right click, then select Open As > Source Code) and paste in the following:
看起来我们已经完成了……但是等等!我们没有做任何事情来启动并运行“打开方式...”菜单!好吧,事实证明 .plist 文件中有一些必要的杂物。打开应用程序 .plist(快速右键单击,然后选择 Open As > Source 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">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFiles</key>
<array/>
<key>CFBundleIdentifier</key>
<string>CodaFi.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIFileSharingEnabled</key>
<true/>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>PDF Document</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.adobe.pdf</string>
</array>
</dict>
</array>
</dict>
</plist>
[Side note: be careful mucking around in the source code of any plist, if you don't know what you're doing, you could get the dreaded 'This file has been corrupted' error from Xcode]
[旁注:小心处理任何 plist 的源代码,如果您不知道自己在做什么,您可能会从 Xcode 中得到可怕的“此文件已损坏”错误]
If one were to right click and select Open As > Property List, it would look like this:
如果右键单击并选择“打开为”>“属性列表”,它将如下所示:
There's another VERY important field in there called 'Application supports iTunes file sharing'. That must be set to "YES", or your app will not show up in iTunes as supporting file sharing.
还有另一个非常重要的字段,称为“应用程序支持 iTunes 文件共享”。必须将其设置为“YES”,否则您的应用程序将不会在 iTunes 中显示为支持文件共享。
The 'Document Types' field specifies the kinds of documents our example can open. Expand the arrow to find its role and UTI's. These are unique identifiers (Unique Type Identifiers; seems obvious what that acronym means now, doesn't it?) that every kind of file has. UTI's are what let the finder replace a generic document image with that nice localized image of the file type (don't believe me, rename an unimportant file extension to .ouhbasdvluhb and try to get a nice picture!) If I wanted to open my own custom format (lets say a .code file) then I would put something like com.CodaFi.code
(reverse DNS notation for those with no clue) in the UTI field and Document Type Name would be 'CodaFi Document'. Handler Rank and Role should be straightforward as our handler rank is alternate (because we don't own the file) and our role is viewer (because we don't need anything more important. Our example is just a viewer and not an editor, so we'll leave it as such.
“文档类型”字段指定我们的示例可以打开的文档类型。展开箭头以查找其作用和 UTI。这些是每种文件都有的唯一标识符(Unique Type Identifiers;这个首字母缩略词现在的含义似乎很明显,不是吗?)。UTI 是什么让查找器用文件类型的漂亮的本地化图像替换通用文档图像(不相信我,将不重要的文件扩展名重命名为 .ouhbasdvluhb 并尝试获得一张漂亮的图片!)如果我想打开我的自己的自定义格式(可以说是 .code 文件)然后我会放一些类似的东西com.CodaFi.code
(对于那些没有线索的人,反向 DNS 表示法)在 UTI 字段和文档类型名称中将是“CodaFi 文档”。处理程序等级和角色应该很简单,因为我们的处理程序等级是交替的(因为我们不拥有文件),我们的角色是查看器(因为我们不需要任何更重要的东西。我们的示例只是查看器而不是编辑器,所以我们就这样吧。
For future reference, UTI's have official system-declared naming schemes when they come from respected sources (Oracle, Microsoft, even Apple itself) which can be found in the Uniform Type Identifier Reference Guide, but are listed herefor pedantry's sake.
为了将来参考,UTI 有官方系统声明的命名方案,当它们来自受人尊敬的来源(甲骨文、微软,甚至苹果本身)时,可以在统一类型标识符参考指南中找到,但为了迂腐而在此处列出。
Now, let's run 'er! The code should build with no errors, assuming you copied verbatim and got those darned xib hookups right. Now when you first launch your application, you should be presented with the option to open a document in iBooks. Deselect it, the real meat of the code is opening other documents! Launch Safari and search for any PDF that Safari can QuickLook or open. Then in the "Open in..." menu, our app shows up! Click it. You'll get the little switcheroo animation and an alert will come up with the location of the file. When you dismiss it, the UIWebView
will have loaded the PDF. The Mail app has similar functionality with attachments. You can also call those PDFs up to your app.
现在,让我们跑吧!假设您逐字复制并正确设置了那些该死的 xib 连接,该代码应该没有错误地构建。现在,当您第一次启动应用程序时,您应该会看到在 iBooks 中打开文档的选项。取消选择它,代码的真正内容是打开其他文件!启动 Safari 并搜索 Safari 可以 QuickLook 或打开的任何 PDF。然后在“打开方式...”菜单中,我们的应用程序出现了!点击它。您将获得小切换动画,并且会显示文件位置的警报。当您关闭它时,UIWebView
将加载 PDF。邮件应用程序具有与附件类似的功能。您还可以将这些 PDF 调用到您的应用程序。
That's it, it's all done. Enjoy and happy coding!
就是这样,一切都完成了。享受和快乐编码!
回答by memmons
There is a superb answer to this question here. I've copied some of the answer below for clarity, but you should refer to that question for the complete answer.
这个问题在这里有一个很好的答案。为了清楚起见,我复制了下面的一些答案,但您应该参考该问题以获取完整答案。
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.
文件类型处理是 iPhone OS 3.2 的新功能,与现有的自定义 URL 方案不同。您可以注册您的应用程序来处理特定的文档类型,任何使用文档控制器的应用程序都可以将这些文档的处理移交给您自己的应用程序。
To register support, you will need to have something like the following in your Info.plist:
要注册支持,您需要在 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>
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:
上面示例中使用的 UTI 之一是系统定义的,但另一个是特定于应用程序的 UTI。需要导出特定于应用程序的 UTI,以便系统上的其他应用程序可以知道它。为此,您需要在 Info.plist 中添加一个部分,如下所示:
<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
.
此特定示例导出com.sunsetlakesoftware.molecules.pdb
具有 .pdb 文件扩展名的UTI,对应于 MIME 类型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.
当附件打开时,您的应用程序将启动,您需要在-application:didFinishLaunchingWithOptions:
应用程序委托方法中处理此文件。似乎以这种方式从 Mail 加载的文件被复制到您的应用程序的 Documents 目录中,该目录位于与它们到达的邮箱对应的子目录下。