在我的iOS Mail和Safari应用程序中支持Open In ...菜单项

时间:2011-10-30 00:30:36

标签: objective-c ios cocoa-touch uidocumentinteraction

我需要让我的应用程序打开来自Safari和Mail应用程序的文档,并在UIDocumentInteractionController类中使用“Open In ...”。我该如何做到这一点?

2 个答案:

答案 0 :(得分:97)

我知道这对我来说是一个非常令人沮丧的程序员,现在甚至是一个技术熟练的程序员。通过Mail和Safari应用程序的文件I / O涉及应用程序本身内的非常有趣的命名约定。因此,让我们为iPhone的Xcode项目弄脏手。打开Xcode(我将在本教程中使用4.2)并选择'单视图'应用程序模板(或创建一个空项目,然后使用.xib添加单个视图)。

Screenshot showing Xcode template selection sheet

在新创建的应用程序中,将视图控制器(和关联的xib)重命名为OfflineReaderViewController,然后我们将查看代码。 (我们将触摸除前缀标题和main.m之外的所有文件,因此请注意,您需要在您面前的所有内容!)

输入AppDelegate标头并将以下代码粘贴到其中:

#import <UIKit/UIKit.h>

@class OfflineReaderViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) OfflineReaderViewController *viewController;

@end

然后输入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

这:

-(BOOL)application:(UIApplication *)application 
               openURL:(NSURL *)url 
     sourceApplication:(NSString *)sourceApplication 
            annotation:(id)annotation 
    {    
        if (url != nil && [url isFileURL]) {
            [self.viewController handleDocumentOpenURL:url];
        }    
        return YES;
    }

这是本教程中最重要的部分。要将其分解为各自的部分:-(BOOL)application:(UIApplication *)application是我们的示例应用程序; openURL:(NSURL *)url是发送给我们的网址,告诉我们要打开的内容; sourceApplication:(NSString *)sourceApplication是发送链接的应用程序;而annotation:(id)annotation是我们不会进入的额外功能。

现在,我们必须布局我们的xib。输入xib(应该被授权&#39; OfflineReaderViewController&#39;,但它与xib无关,除非我们调用initWithNibName:(我们赢了),然后制作它看起来如下图所示:

Screenshot of IB layout

进入UIWebView属性并检查&#34;缩放页面以适应&#34;非常重要,因为这样我们可以放大和缩小有捏的网页。不要担心这些关系,我们很快就会创建它们。

输入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

现在是.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

那些正在积极观看并且不仅仅是复制我告诉你的所有事情(开玩笑)的人都会知道这一行:[[NSBundle mainBundle] pathForResource:@"Minore" ofType:@"pdf"];会给我们一个SIGABRT,因为,文件并没有存在!那么,拖动你从哪里拉出的任何通用PDF(我推荐here因为谁没有花时间阅读大量的文档?),然后复制它的标题并用后缀粘贴它(.pdf)已删除; ofType:@"pdf"部分为我们处理。完成后,该行应如下所示:[[NSBundle mainBundle] pathForResource:@"//file name//" ofType:@"pdf"];

现在回到xib并连接那些IBOutlets!总而言之,这就是你的&#34;文件所有者&#34;选项卡应如下所示:

Screenshot showing established connections

我们似乎已经完成了......但是等等!我们没有做任何事情来获得一个&#34; Open In ...&#34;菜单启动并运行!好吧,事实证明,必须在.plist文件中存在一些问题。打开app .plist(右键快速右键,然后选择Open As&gt; 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>

[旁注:在任何plist的源代码中都要小心,如果你不知道自己在做什么,你可能会感到害怕&#39;这个文件已经损坏&#39; 39;来自Xcode的错误]

如果要右键单击并选择Open As&gt;财产清单,它看起来像这样:

Shot of Xcode plist editor window

那里还有另一个非常重要的领域叫做“应用程序支持iTunes文件共享”。必须设置为&#34; YES&#34;或者您的应用不会在iTunes中显示为支持文件共享。

&#39;文档类型&#39; field指定我们的示例可以打开的文档类型。展开箭头以查找其角色和UTI。这些是各种文件所具有的唯一标识符(唯一类型标识符;这些首字母缩写词现在意味着什么,并不是什么?)。 UTI是让发现者用文件类型的漂亮本地化图像替换通用文档图像的原因(不要相信我,将不重要的文件扩展名重命名为.ouhbasdvluhb并尝试获得漂亮的图片!)如果我想打开自己的自定义格式(比如说.code文件),那么我会在UTI字段中输入类似com.CodaFi.code的内容(对于没有线索的人反向DNS表示法),文档类型名称将为&# 39; CodaFi文件&#39;。处理程序排名和角色应该是直截了当的,因为我们的处理程序排名是替代的(因为我们不拥有该文件)而我们的角色是查看者(因为我们不需要更重要的东西。我们的例子只是一个查看器和不是编辑,所以我们将其留下。

为了将来参考,UTI有来自受尊敬的来源(甲骨文,微软,甚至是苹果本身)的官方系统声明的命名方案,这些来源可以在Uniform Type Identifier Reference Guide中找到,但列出{ {3}}出于迂腐的缘故。

现在,让我们跑吧!代码应该构建没有错误,假设你逐字复制并使那些darned xib连接正确。现在,当您首次启动应用程序时,您应该会看到在iBooks中打开文档的选项。取消选择它,代码的真正含义是打开其他文档!启动Safari并搜索Safari可以QuickLook或打开的任何PDF。然后在&#34;打开......&#34;菜单,我们的应用程序显示!点击它。您将获得小型的switchheroo动画,并会提供文件位置的警报。当你解雇它时,UIWebView将加载PDF。 Mail应用程序具有与附件类似的功能。您也可以将这些PDF调用到您的应用程序。

这就是它,它已经完成了。享受和快乐的编码!

答案 1 :(得分:6)

这个问题有一个极好的答案here。为了清楚起见,我已经复制了下面的一些答案,但你应该参考那个问题来获得完整的答案。

文件类型处理是iPhone OS 3.2的新增功能,与现有的自定义URL方案不同。您可以注册您的应用程序以处理特定的文档类型,任何使用文档控制器的应用程序都可以将这些文档的处理交给您自己的应用程序。

要注册支持,您需要在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>

上面示例中使用的一个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>

此特定示例使用.pdb文件扩展名导出com.sunsetlakesoftware.molecules.pdb UTI,对应于MIME类型chemical/x-pdb

有了这个,您的应用程序将能够处理附加到电子邮件或系统上其他应用程序的文档。在Mail中,您可以点按并按住以显示可以打开特定附件的应用程序列表。

打开附件后,您的应用程序将启动,您需要在-application:didFinishLaunchingWithOptions:应用程序委托方法中处理此文件。看来,以这种方式从Mail加载的文件被复制到应用程序的Documents目录下,该子目录对应于它们到达的电子邮箱。