web-dev-qa-db-ja.com

iOS MailおよびSafariのアプリで[開く...]メニュー項目をサポートする

UIDocumentInteractionControllerクラスの "Open In ..."を使用して、SafariおよびMailアプリからドキュメントを開く必要があります。どうすればこれを達成できますか?

49
CodaFi

これは初心者プログラマーとして、あるいは今や中程度のスキルを持つプログラマーとして私にとって非常にイライラすることでした。メールアプリとSafariアプリを介したファイルI/Oには、アプリ自体の中で非常に興味深い命名規則が含まれます。それでは、iPhone用のXcodeプロジェクトで手を汚しましょう。 Xcodeを開き(このチュートリアルでは4.2を使用します)、「シングルビュー」アプリケーションテンプレートを選択します(または空のプロジェクトを作成してから、.xibで単一のビューを追加します)。

Screenshot showing Xcode template selection sheet

その新しく作成されたアプリケーションで、View Controller(および関連する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
_

次に、デリゲートの.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は、何を開くかを伝えるために送信されるURLです。 sourceApplication:(NSString *)sourceApplicationは、リンクを送信したアプリケーションです。そしてannotation:(id)annotationは、私たちが入らない追加機能です。

次に、xibをレイアウトする必要があります。 xib(「OfflineReaderViewController」というタイトルにする必要がありますが、_initWithNibName:_(これはしません)を呼び出さない限り、xibには関係ありません)を入力し、下の図のようにします。

Screenshot of IB layout

UIWebViewの属性に移動し、[ページを拡大縮小する]チェックボックスをオンにすることは非常に重要です。これにより、ピンチのあるWebページをズームインおよびズームアウトできます。まだ接続について心配する必要はありません。まもなく作成します。

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を接続します!以上が、「ファイルの所有者」タブの外観です。

Screenshot showing established connections

完了したようですが...待ってください! 「開く...」メニューを起動して実行するために何もしませんでした!さて、必要な.plistファイルにはいじくり回されていることがあります。アプリの.plistを開き(右クリックして、[名前を付けて開く]> [ソースコード]を選択します)、以下を貼り付けます。

_<?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のソースコードを慎重にいじってください。何をしているのかわからない場合は、Xcodeから恐ろしい「このファイルは破損しています」というエラーが表示されることがあります]

右クリックして[名前を付けて開く]> [プロパティリスト]を選択すると、次のようになります。

Shot of Xcode plist editor window

「アプリケーションがiTunesファイル共有をサポートする」と呼ばれるもう1つの非常に重要なフィールドがあります。これを「YES」に設定する必要があります。そうしないと、アプリがファイル共有をサポートするものとしてiTunesに表示されません。

[ドキュメントタイプ]フィールドは、この例で開くことができるドキュメントの種類を指定します。矢印を展開して、その役割とUTIを見つけます。これらはすべての種類のファイルが持つ一意の識別子です(一意のタイプ識別子。頭字語の意味が明らかになったようですね)。 UTIは、Finderが汎用ドキュメントイメージをそのファイルタイプのそのニースのローカライズされたイメージに置き換えることを可能にします(私を信じないで、重要でないファイル拡張子を.ouhbasdvluhbに変更し、ニースの画像を取得しようとします!)独自のカスタム形式(.codeファイルと言う)を使用すると、UTIフィールドに_com.CodaFi.code_(手がかりのない人には逆DNS表記)のようなものを入れ、Document Type Nameは「CodaFi Document」になります。ハンドラランクとロールは、ファイルの所有者ではないためハンドラランクが交互になり、ビューアーであるため(より重要なものは必要ないためです。この例はエディターではなくビューアーにすぎません。そのままにしておきます。

将来の参照のために、UTIには、 niform Type Identifier Reference)にある尊敬されるソース(Oracle、Microsoft、さらにはApple自体)からのものである場合、公式のシステム宣言命名スキームがあります。ガイド 、ただしリストされています ここ 学問のため。

さあ、走りましょう!コードをそのままコピーし、逐語的にコピーし、それらのdarned xibフックアップを正しく作成すると仮定すると、コードはエラーなしでビルドされます。これで、アプリケーションを初めて起動したときに、iBooksでドキュメントを開くオプションが表示されます。選択を解除すると、コードの本質は他のドキュメントを開くことになります! Safariを起動し、SafariでQuickLookまたは開くことができるPDFを検索します。[...で開く]メニューにアプリが表示されます!クリックしてください。小さなスイッチャーが表示されます。アニメーションと警告が表示され、ファイルの場所が表示されます。ファイルを閉じると、UIWebViewがPDFをロードします。メールアプリにも同様の機能があり、添付ファイルがあります。アプリ。

これで完了です。楽しんで幸せなコーディング!

97
CodaFi

この質問に対する素晴らしい答えがあります 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の1つはシステム定義でしたが、もう1つはアプリケーション固有の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>

この特定の例では、com.sunsetlakesoftware.molecules.pdb MIMEタイプに対応する.pdbファイル拡張子を持つUTI chemical/x-pdb

これにより、アプリケーションは電子メールまたはシステム上の他のアプリケーションから添付されたドキュメントを処理できるようになります。 Mailでは、特定の添付ファイルを開くことができるアプリケーションのリストをタップアンドホールドで表示できます。

添付ファイルを開くと、アプリケーションが起動します。このファイルの処理は、-application:didFinishLaunchingWithOptions:アプリケーションデリゲートメソッド。この方法でMailからロードされたファイルは、到着したメールボックスに対応するサブディレクトリの下のアプリケーションのDocumentsディレクトリにコピーされるようです。

6
memmons