玩轉iOS開發:iOS 11 新特性《基於文件管理的App》

CainLuo發表於2017-12-14

文章轉至我的個人部落格: https://cainluo.github.io/15132142909284.html


眾所周知iOS是一個封閉的系統, 每個App都有一個屬於它們自己的沙盒, AppApp之間是不可以互相訪問的, 也由於這一點, iOS也可以被稱為安全的系統.

但這又會帶來另一個問題, 就是在管理檔案的方式上比較複雜, 使用者無法自由的瀏覽檔案, 我們需要專門的工具與流程才能在iOS上的應用程式之間共享檔案.

為了解決這個問題, 蘋果爸爸在iOS 11里加入了一個用於管理檔案的新工具:UIDocumentBrowserViewController.

這個全新的檢視控制器可以讓我們建立文件瀏覽器的App, 就像iOS 11上的新的檔案App一樣, 可以讓我們自由管理所有可用位置上的文件.

PS: 這篇文章所演示的Demo在模擬器上是不能正常工作的, 最好備好iOS 11的裝置來構建和測試該App.

轉載宣告:如需要轉載該文章, 請聯絡作者, 並且註明出處, 以及不能擅自修改本文.

開始前的知識補充

在開始建立這個專案之前, 我們要了解iOS Document的工作原理, UIDocument是一個幾乎執行在所有基於文件的應用的強大類.

UIDocument是一個物理檔案的包裝, 它可以在我們的裝置, iCloud上非同步讀取/寫入檔案, 自動儲存, 檔案版本控制等等.

雖然我們可以不用強制性使用UIDocument, 但我還是強烈建議去使用, 因為它可以幫我們省下很多事情, 比如我們需要在iPhoneMacBook上使用同一個iCloud檔案, UIDocument就可以幫我們處理所有的事情.

文件提供者

提供文件的App擴充套件允許其他App在你的檔案上進行修改, 避免沙盒化.

UIDocumentPickerViewController就是該擴充套件的一部分, 這是其他App將顯示你的App中選擇文件的視覺化介面.

提供文件的App允許其他App從它本身內匯入檔案, 這也就說明這個檔案會被拷貝一份, 而原始檔案則會保持不變.

當然我們也可以允許其他App直接開啟提供文件的App, 直接選擇檔案進行處理並覆蓋原始檔.

文件瀏覽器

這裡我們就講UIDocumentBrowserViewController, 它是一種App的型別, 並不是我們所寫的App的副檔名.

我們定製的UIDocumentBrowserViewController子類必須是App的根檢視, 換一句話說, 這就是iOS 11文件型別App的自定義實現.

開始寫程式碼

這裡我們就拿顏色管理來作為場景, 在檔案瀏覽器上去管理這些RGB顏色值, 它的副檔名也定義為.color.

我們在建立好工程之後, 需要在Info.plist裡把UISupportsDocumentBrowser設定為YES.

自定義擴充套件

我們在使用.color副檔名的檔案時, 需要用逗號分隔RGB顏色值, 比如白色的話, 我們就會宣告為255,255,255.

在此, 如果要使得文件瀏覽器中支援自定義的.color副檔名, 我們需要註冊一個新的同意型別識別符號(UTI這個東西在上一章文章的末尾就有介紹, ), 然後我們需要將新的檔案與我們的App關聯.

開啟我們的專案找到Info這個選項, 然後把我們自定義的內容填好:

1

Exported UTIs所填寫的內容:

  • Description: 檔名的描述, 這裡輸入RGB Color File
  • Identifier: 檔案唯一識別符號, 這裡我們輸入為com.cainluo.colorExtension
  • Conforms To: UTI可以符合其他型別, 就好像父類子類一樣, 這裡我們就寫public.text, 因為我們的.color也是簡單的文字型別, 如果你是HTML的話, 那你可以寫成public.html.

寫完這個之後, 我們還需要指定副檔名, 展開Additional exported UTI properties, 填入一個UTTypeTagSpecification字典, 然後在這個字典裡建立一個名為public.filename-extension的陣列, 最後再填入一個color物件.

定義好這個UTI之後, 我們需要在Document Types填入對應的NameTypes, Name就填入Color Extension, Types就填入com.razeware.colorExtension.

2

如果你想了解更多關於UTI的內容, 可以去apple.co/2v0FiHO看看.

開始寫程式碼

建立好對應的控制器之後, 我們要設定一下:

    self.delegate = self;
    self.allowsDocumentCreation = YES;
複製程式碼

然後遵守UIDocumentBrowserViewControllerDelegateUIViewControllerTransitioningDelegate兩個協議, 並且實現它們的代理方法:

#pragma mark - UIViewControllerTransitioningDelegate
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                            presentingController:(UIViewController *)presenting
                                                                                sourceController:(UIViewController *)source {

    return self.documentBrowserTransitionController;
}

#pragma mark - UIDocumentBrowserViewControllerDelegate
- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
didRequestDocumentCreationWithHandler:(void(^)(NSURL *_Nullable urlToImport, UIDocumentBrowserImportMode importMode))importHandler {
    
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"ColorFile"
                                         withExtension:@"color"];
    
    importHandler(url, UIDocumentBrowserImportModeCopy);
}

- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
 didImportDocumentAtURL:(NSURL *)sourceURL
       toDestinationURL:(NSURL *)destinationURL {
 
    [self presentColorControllerWithDocument:[[ColorDocument alloc] initWithFileURL:destinationURL]];
}

- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
failedToImportDocumentAtURL:(NSURL *)documentURL
                  error:(NSError * _Nullable)error {
    
    [self showAlertViewControllerWithTitle:@"Failed" message:@"Failed to import"];
}


- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
    didPickDocumentURLs:(NSArray <NSURL *> *)documentURLs {
 
    [self presentColorControllerWithDocument:[[ColorDocument alloc] initWithFileURL:documentURLs[0]]];
}

- (NSArray<__kindof UIActivity *> *)documentBrowser:(UIDocumentBrowserViewController *)controller
               applicationActivitiesForDocumentURLs:(NSArray <NSURL *> *)documentURLs {
  
    ColorDocument *colorDocument = [[ColorDocument alloc] initWithFileURL:documentURLs[0]];
    
    return @[[[DocumentActivity alloc] initDocumentActivityWithColorDocument:colorDocument]];
}
複製程式碼

另外我們還需要配置一些東西, 並且使得可以跳轉到我們需要跳轉的編輯頁面:

#pragma mark - Present Controller
- (void)presentColorControllerWithDocument:(ColorDocument *)colorDocument {
    
    UIStoryboard *mineStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    
    ColorController *colorController = [mineStoryboard instantiateViewControllerWithIdentifier:@"ColorController"];
    
    colorController.colorDocument = colorDocument;
    colorController.transitioningDelegate = self;
    
    self.documentBrowserTransitionController = [self transitionControllerForDocumentURL:colorDocument.fileURL];
    
    [self presentViewController:colorController animated:YES completion:nil];
}
複製程式碼

解釋一下代理方法:

  1. 在成功匯入新文件的時候會呼叫
- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
 didImportDocumentAtURL:(NSURL *)sourceURL
       toDestinationURL:(NSURL *)destinationURL;
複製程式碼
  1. 這是在將要呈現新文件時會呼叫, 它接受一個ColorDocument物件, 而ColorDocument又是UIDocument的子類, 負責儲存和載入色彩檔案, 待會我們就會ColorController裡預覽和編輯顏色檔案.
- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
didRequestDocumentCreationWithHandler:(void(^)(NSURL *_Nullable urlToImport, UIDocumentBrowserImportMode importMode))importHandler;
複製程式碼
  1. 在新文件無法匯入時的時候就會呼叫, 比如當我們無權訪問importHandler回撥中傳遞的檔案時.
- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
failedToImportDocumentAtURL:(NSURL *)documentURL
                  error:(NSError * _Nullable)error;
複製程式碼
  1. 當使用者選擇要在文件瀏覽器中開啟檔案時, 就會呼叫該方法, UIDocumentBrowserViewController支援開啟多個檔案, 但我們這裡只需要使用一個, 所以就只去第一個.
- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
    didPickDocumentURLs:(NSArray <NSURL *> *)documentURLs;
複製程式碼

預覽與編輯介面

開啟ColorController之後, 我們需要設定一下, 看看是否可以讀取對應的檔案:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    ColorDocument *colorDocument = self.colorDocument;
    
    if (!colorDocument) {
        return;
    }
    
    if (colorDocument.documentState == UIDocumentStateNormal) {
        
        [self configControllerUI];
        
    } else {
        
        [colorDocument openWithCompletionHandler:^(BOOL success) {
            
            if (success) {
                
                [self configControllerUI];

            } else {
                
                [self showAlertViewControllerWithTitle:@"Error"
                                               message:@"Can't Open Document"];
            }
        }];
    }
}
複製程式碼

為了可以儲存修改後的內容, 這裡用了一個儲存的方法:

- (IBAction)saveColorModel:(UIButton *)sender {
    
    ColorDocument *colorDocument = self.colorDocument;
    
    if (!colorDocument) {
        return;
    }

    colorDocument.colorModel = [[ColorModel alloc] initColorModelWithRedValue:self.redSlider.value
                                                                   greenValue:self.greenSlider.value
                                                                    blueValue:self.blueSlider.value];
    
    [colorDocument saveToURL:colorDocument.fileURL
            forSaveOperation:UIDocumentSaveForOverwriting
           completionHandler:^(BOOL success) {
        
               if (success) {
                   
                   [self showAlertViewControllerWithTitle:@"Success"
                                                  message:@"Saved file"];

               } else {
                   
                   [self showAlertViewControllerWithTitle:@"Error"
                                                  message:@"Failed to save file"];
               }
           }];
}
複製程式碼

從其他App中開啟

如果我們直接從檔案App中開啟顏色檔案, 你會發現我們的App是開啟了, 但不會工作.

那是因為我們的App.color副檔名關聯, 但我們的編輯頁面並沒有顯示.

由於這個檔案是在別的App裡開啟的, 所以我們需要在AppDelegate裡處理這個事情:

- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    
    if (!url.isFileURL) {
        
        return NO;
    }
    
    DocumentBrowserController *documentBrowserController = (DocumentBrowserController *)self.window.rootViewController;
    
    if (!documentBrowserController) {
        return NO;
    }
    
    [documentBrowserController revealDocumentAtURL:url
                                    importIfNeeded:YES
                                        completion:^(NSURL * _Nullable revealedDocumentURL, NSError * _Nullable error) {
                                            
                                            if (error) {
                                                return;
                                            }
    
                                            [documentBrowserController presentColorControllerWithDocument:[[ColorDocument alloc] initWithFileURL:revealedDocumentURL]];
                                        }];
    
    return YES;
}
複製程式碼

自定義文件瀏覽器

在這裡面我們還可以自定義一下我們的文件瀏覽器樣式:

#pragma mark - Custom Document Browser Controller
- (void)customDocumentBrowserController {
    
    self.view.tintColor = [UIColor colorNamed:@"MarineBlue"];
    
    self.browserUserInterfaceStyle = UIDocumentBrowserUserInterfaceStyleLight;
    
    UIDocumentBrowserAction *documentBrowserAction = [[UIDocumentBrowserAction alloc] initWithIdentifier:@"com.cainluo.action"
                                                                                          localizedTitle:@"Lighter Color"
                                                                                            availability:UIDocumentBrowserActionAvailabilityMenu
                                                                                                 handler:^(NSArray<NSURL *> * _Nonnull urls) {
        
                                                                                                     ColorDocument *colorDocument = [[ColorDocument alloc] initWithFileURL:urls[0]];
                                                                                                     
                                                                                                     [colorDocument openWithCompletionHandler:^(BOOL success) {
                                                                                                         
                                                                                                         if (success) {
                                                                                                             
                                                                                                             colorDocument.colorModel = [colorDocument.colorModel lighterColorWithToAdd:60];
                                                                                                             
                                                                                                             [self presentColorControllerWithDocument:colorDocument];
                                                                                                         }
                                                                                                     }];
                                                                                                 }];
    
    documentBrowserAction.supportedContentTypes = @[@"com.cainluo.colorExtension"];
    
    self.customActions = @[documentBrowserAction];
    
    UIBarButtonItem *aboutButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"About"
                                                                        style:UIBarButtonItemStylePlain
                                                                       target:self
                                                                       action:@selector(openAbout)];
    
    self.additionalTrailingNavigationBarButtonItems = @[aboutButtonItem];
}

- (void)openAbout {
    
    [self showAlertViewControllerWithTitle:@"關於我們" message:@"Color Document 1.0 by Cain Luo"];
}
複製程式碼

這個顏色值是設定在Assets.xcassets中.

動畫控制器

我們可以為簡報新增一個動畫:

@property (nonatomic, strong) UIDocumentBrowserTransitionController *documentBrowserTransitionController;
複製程式碼
#pragma mark - UIViewControllerTransitioningDelegate
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                            presentingController:(UIViewController *)presenting
                                                                                sourceController:(UIViewController *)source {

    return self.documentBrowserTransitionController;
}
複製程式碼

我們剛剛的那個present方法裡就寫好了對應的配置:

    colorController.transitioningDelegate = self;
    
    self.documentBrowserTransitionController = [self transitionControllerForDocumentURL:colorDocument.fileURL];
複製程式碼

自定義活動

最後面我們還可以新增多一個活動列表, 這個時候我們就需要定義一個UIActivity的自雷, 並且將.color檔案以字串的形式複製到貼上板上:

- (instancetype)initDocumentActivityWithColorDocument:(ColorDocument *)colorDocument {
    
    self = [super init];

    if (self) {
        
        self.colorDocument = colorDocument;
    }
    
    return self;
}

+ (UIActivityCategory)activityCategory {
    
    return UIActivityCategoryAction;
}

- (UIActivityType)activityType {
    return @"ColorBrowserCopy";
}

- (NSString *)activityTitle {
    
    return @"Color Copy";
}

- (UIImage *)activityImage {
    
    return [UIImage imageNamed:@"copy_activity_icon"];
}

- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems {
    
    return YES;
}

- (void)performActivity {
    
    [self.colorDocument openWithCompletionHandler:^(BOOL success) {
        
        if (success) {
            
            NSString *string = [self.colorDocument stringRepresentation];
            
            if (string) {
                
                [UIPasteboard generalPasteboard].string = string;
                
                [self activityDidFinish:YES];
            }
        }
    }];
}
複製程式碼

最後我們用一個代理方法設定對應的活動事件:

#pragma mark - Custom Activity
- (NSArray<__kindof UIActivity *> *)documentBrowser:(UIDocumentBrowserViewController *)controller
               applicationActivitiesForDocumentURLs:(NSArray <NSURL *> *)documentURLs {
  
    ColorDocument *colorDocument = [[ColorDocument alloc] initWithFileURL:documentURLs[0]];
    
    return @[[[DocumentActivity alloc] initDocumentActivityWithColorDocument:colorDocument]];
}
複製程式碼

最終效果

3

4

工程

https://github.com/CainLuo/iOS-11-Characteristic/tree/master/6.Document

最後

碼字很費腦, 看官賞點飯錢可好

支付寶

相關文章