玩转iOS开发:iOS 11 新特性《基于文档管理的App》

文章转至我的个人博客: 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这个选项, 然后把我们自定义的内容填好:

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.

如果你想了解更多关于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]];
}

最终效果

工程

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

最后

码字很费脑, 看官赏点饭钱可好

相关推荐