UIDocumentInteractionController之程式間文件共享

SuperDanny發表於2017-12-13

iOS中的沙盒可以讓平臺更加的安全,這也是沙盒給使用者帶來的最主要好處。不過由於沙盒的嚴格限制,導致程式之間共享資料比較麻煩。一般在程式間共享文件可以通過UIDocumentInteractionController類實現通訊。它支援在你的app中用其他app預覽和顯示文件。同時也支援檔案關聯,允許其他app通過你的程式開啟檔案。這些技術包括了UIKit中提供的UIDocumentInteractionController類(UIDocumentInteractionController Class Reference),以及Quick Look框架(Quick Look Framework Reference)。

本文將就如何在應用之間進行檔案共享進行基本探究。還請大牛勿噴。

蘋果官方文件

效果圖

檔案共享

跨APP傳檔案

預覽文件和呈現選項選單

如果你的app需要開啟它不支援的檔案(PDF檔案、影象檔案,等等),或者需要將app的檔案傳輸給另外一個允許接收此型別檔案的app時。可以使用檔案互動控制器(UIDocumentInteractionController類的例項)為使用者提供可接收程式來處理檔案,說的簡單點就是通過Quick Look框架判斷文件是否能被另一個app開啟和預覽。

UIDocumentInteractionController在iOS3.2中就已經存在了,使用起來非常靈活,功能也比較強大。它除了支援同裝置上app之間的文件共享外,還可以實現文件的預覽、列印、發郵件以及複製。

要使用一個檔案互動控制器(UIDocumentInteractionController類的例項),需要以下步驟:

  1. 為每個你想開啟的檔案建立一個UIDocumentInteractionController類的例項
  2. 實現UIDocumentInteractionControllerDelegate代理
  3. 顯示預覽視窗/顯示選單。

一、建立例項

DocumentInteraction Controller使用靜態方法interactionControllerWithURL建立例項,這個方法使用一個NSURL作為引數。

//建立例項
NSURL *filePath = [NSURL fileURLWithPath:path];

UIDocumentInteractionController *documentController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
複製程式碼

二、顯示預覽視窗

Document Interaction Controller物件使用presentPreviewAnimated方法彈出一個全屏的文件預覽視窗。

BOOL b = [documentController presentPreviewAnimated:YES];
複製程式碼

三、顯示選單

如果你不想在本應用裡面開啟檔案,那麼可以通過第三方應用開啟預覽檔案。通過OptionsMenu(選項選單),顯示能夠接收該型別檔案的應用,由使用者選擇相應的操作。

顯示選單可以使用下列方法:

- presentOptionsMenuFromRect:inView:animated:
- presentOptionsMenuFromBarButtonItem:animated:
- presentOpenInMenuFromRect:inView:animated:
- presentOpenInMenuFromBarButtonItem:animated:
複製程式碼

這些方法都是類似的,只是顯示位置有區別而已。以下程式碼演示其中一個方法的使用。

CGRect navRect = self.navigationController.navigationBar.frame;
navRect.size = CGSizeMake(1500.0f, 40.0f);
[documentController presentOptionsMenuFromRect:navRect
                                        inView:self.view
                                      animated:YES];
複製程式碼

四、使用委託

如果你顯示一個Document Interaction Controller ,則必需要為delegate屬性用指定一個委託。讓委託告訴DocumentInteraction Controller如何顯示。

documentController.delegate = self;
複製程式碼

委託物件需要實現一系列委託方法,最常見的包括:

- documentInteractionControllerViewControllerForPreview:
- documentInteractionControllerViewForPreview:
- documentInteractionControllerRectForPreview:
複製程式碼

這3個方法在使用者點選“快速檢視”選單時依次呼叫。

- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller {
    return self;
}
- (UIView *)documentInteractionControllerViewForPreview:(UIDocumentInteractionController *)controller {
    return self.view;
}

- (CGRect)documentInteractionControllerRectForPreview:(UIDocumentInteractionController *)controller {
  return self.view.frame;
 }
 
//點選預覽視窗的“Done”(完成)按鈕時呼叫
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller {
}
複製程式碼

功能一:分享檔案

- (void)shareFile {
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"皮卡丘"
                                                          ofType:@"jpeg"];
                                                          
    //建立例項
    UIDocumentInteractionController *documentController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
    
    //設定代理
    documentController.delegate = self;
    BOOL canOpen = [documentController presentOpenInMenuFromRect:CGRectZero
                                                          inView:self.view
                                                        animated:YES];
    if (!canOpen) {
        NSLog(@"沒有程式可以開啟要分享的檔案");
    }
}
複製程式碼

功能二:預覽檔案(註冊應用程式支援的檔案型別)

- (void)preview {
    if (!_path) {
        return;
    }

    NSURL *fileURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self getURL], [_path lastPathComponent]]];
    
    //建立例項
    UIDocumentInteractionController *documentController =
    [UIDocumentInteractionController
     interactionControllerWithURL:fileURL];
     
    //設定代理
    documentController.delegate = self;

    [documentController presentPreviewAnimated:YES];
}
複製程式碼

配置Info.plist檔案

如果你的程式能夠開啟某種檔案,你可以向系統進行註冊。方便其他程式通過 iOS 的document interaction技術提供給使用者一個選擇,從而呼叫你的程式處理這些檔案。

這需要在程式的Info.plist檔案中新增CFBundleDocumentTypes鍵(檢視CoreFoundation Keys)。

系統將該鍵中包含的內容進行登記,這樣其他程式就可以通過document interaction controller訪問到這些資訊。

CFBundleDocumentTypes鍵是一個dictionary陣列,每個dictionary表示了一個指定的文件型別。一個文件型別通常與某種檔案型別是一一對應的。

但是,如果你的程式對多個檔案型別採用同樣的處理方式,你也可以把這些型別都分成一個組,統一視作一個文件型別。例如,你的程式中使用到的本地文件型別,有一個是舊格式的,還有一個新格式(似乎是影射微軟office文件),則你可以將二者分成一組,都放到同一個文件型別下。這樣,舊格式和新格式的檔案都將顯示為同一個文件型別,並以同樣的方式開啟。

CFBundleDocumentTypes陣列中的每個 dictionary 可能包含以下鍵:

  • CFBundleTypeName

指定文件型別名稱。

  • CFBundleTypeIconFiles

是一個陣列,包含多個圖片檔名,用於作為該文件的圖示。

  • LSItemContentTypes

是一個陣列,包含多個UTIUniform Type Identifiers】型別的字串。UTI型別是本文件型別(組)所包含的檔案型別。

  • LSHandlerRank

表示應用程式是“擁有”還是僅僅是“開啟”這種型別而已。

下表列出了Info.plist中的一個CFBundleTypeName官方示例。

  1. 自定義檔案格式的文件型別
<dict>
  <key>CFBundleTypeName</key>
  <string>My File Format</string>
  <key>CFBundleTypeIconFiles</key>
      <array>
          <string>MySmallIcon.png</string>
          <string>MyLargeIcon.png</string>
      </array>
  <key>LSItemContentTypes</key>
      <array>
          <string>com.example.myformat</string>
      </array>
  <key>LSHandlerRank</key>
  <string>Owner</string>
</dict>
複製程式碼
  1. 自己程式配置檔案
<key>CFBundleDocumentTypes</key>
   <array>
   	<dict>
   		<key>CFBundleTypeName</key>
   		<string>com.myapp.common-data</string>
   		<key>LSItemContentTypes</key>
   		<array>
   			<string>com.microsoft.powerpoint.ppt</string>
   			<string>public.item</string>
   			<string>com.microsoft.word.doc</string>
   			<string>com.adobe.pdf</string>
   			<string>com.microsoft.excel.xls</string>
   			<string>public.image</string>
   			<string>public.content</string>
   			<string>public.composite-content</string>
   			<string>public.archive</string>
   			<string>public.audio</string>
   			<string>public.movie</string>
   			<string>public.text</string>
   			<string>public.data</string>
   		</array>
   	</dict>
   </array>
複製程式碼

開啟支援的檔案型別

你可以在應用程式委託的application:didFinishLaunchingWithOptions:方法中獲得該檔案的資訊。如果你的程式要處理某些自定義的檔案型別,你必須實現這個委託方法(而不是applicationDidFinishLaunching: 方法) 並用這個方法啟動應用程式。

application:didFinishLaunchingWithOptions:方法的option引數包含了要開啟的檔案的相關資訊。尤其需要在程式中關心下列鍵:

  • UIApplicationLaunchOptionsURLKey

包含了該檔案的NSURL。

  • UIApplicationLaunchOptionsSourceApplicationKey

包含了傳送請求的應用程式的 Bundle ID。

  • UIApplicationLaunchOptionsAnnotationKey

包含了源程式向目標程式傳遞的與該檔案相關的屬性列表物件。

如果UIApplicationLaunchOptionsURLKey鍵存在,你的程式應當立即用該 URL 開啟該檔案並將內容呈現給使用者。其他鍵可用於收集與開啟的檔案相關的引數和資訊。

如果你的應用程式處於活躍狀態,此時application:didFinishLaunchingWithOptions:方法是不會被呼叫的。需要實現application:openURL:options:方法

【以下是本人的寫法】

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    _vc = [[ViewController alloc] init];
    UINavigationController *nav    = [[UINavigationController alloc] initWithRootViewController:_vc];
    
    self.window                    = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor    = [UIColor whiteColor];
    self.window.rootViewController = nav;
    [self.window makeKeyAndVisible];
    
    if (launchOptions) {
        NSString *str = [NSString stringWithFormat:@"\n傳送請求的應用程式的 Bundle ID:%@\n\n檔案的NSURL:%@\n\n檔案相關的屬性列表物件:%@",
                         launchOptions[UIApplicationLaunchOptionsSourceApplicationKey],
                         launchOptions[UIApplicationLaunchOptionsURLKey],
                         launchOptions[UIApplicationLaunchOptionsSourceApplicationKey]];
        
        [[[UIAlertView alloc] initWithTitle:@""
                                    message:str
                                   delegate:nil
                          cancelButtonTitle:@"確定"
                          otherButtonTitles:nil, nil] show];
        
        _vc.path = [launchOptions[UIApplicationLaunchOptionsURLKey] description];
        [_vc preview];
    }
    return YES;
}

- (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<NSString *,id> *)options {
    if (options) {
        NSString *str = [NSString stringWithFormat:@"\n傳送請求的應用程式的 Bundle ID:%@\n\n檔案的NSURL:%@", options[UIApplicationOpenURLOptionsSourceApplicationKey], url];
        
        [[[UIAlertView alloc] initWithTitle:@""
                                    message:str
                                   delegate:nil
                          cancelButtonTitle:@"確定"
                          otherButtonTitles:nil, nil] show];

        _vc.path = [url description];
        [_vc preview];
    }
    return YES;
}
複製程式碼

再一次感謝您花費時間閱讀這篇文章!

微博: @Danny_呂昌輝
部落格: SuperDanny

2015 年 12月 26日

相關文章