文章轉至我的個人部落格: https://cainluo.github.io/15132142909284.html
眾所周知iOS
是一個封閉的系統, 每個App
都有一個屬於它們自己的沙盒, App
與App
之間是不可以互相訪問的, 也由於這一點, iOS
也可以被稱為安全的系統.
但這又會帶來另一個問題, 就是在管理檔案的方式上比較複雜, 使用者無法自由的瀏覽檔案, 我們需要專門的工具與流程才能在iOS
上的應用程式之間共享檔案.
為了解決這個問題, 蘋果爸爸在iOS 11
里加入了一個用於管理檔案的新工具:UIDocumentBrowserViewController
.
這個全新的檢視控制器可以讓我們建立文件瀏覽器的App
, 就像iOS 11
上的新的檔案App
一樣, 可以讓我們自由管理所有可用位置上的文件.
PS: 這篇文章所演示的
Demo
在模擬器上是不能正常工作的, 最好備好iOS 11
的裝置來構建和測試該App
.
轉載宣告:如需要轉載該文章, 請聯絡作者, 並且註明出處, 以及不能擅自修改本文.
開始前的知識補充
在開始建立這個專案之前, 我們要了解iOS Document
的工作原理, UIDocument
是一個幾乎執行在所有基於文件的應用的強大類.
UIDocument
是一個物理檔案的包裝, 它可以在我們的裝置, iCloud
上非同步讀取/寫入檔案, 自動儲存, 檔案版本控制等等.
雖然我們可以不用強制性使用UIDocument
, 但我還是強烈建議去使用, 因為它可以幫我們省下很多事情, 比如我們需要在iPhone
和MacBook
上使用同一個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
填入對應的Name
和Types
, Name
就填入Color Extension
, Types
就填入com.razeware.colorExtension
.
如果你想了解更多關於UTI
的內容, 可以去apple.co/2v0FiHO看看.
開始寫程式碼
建立好對應的控制器之後, 我們要設定一下:
self.delegate = self;
self.allowsDocumentCreation = YES;
複製程式碼
然後遵守UIDocumentBrowserViewControllerDelegate
和UIViewControllerTransitioningDelegate
兩個協議, 並且實現它們的代理方法:
#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];
}
複製程式碼
解釋一下代理方法:
- 在成功匯入新文件的時候會呼叫
- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
didImportDocumentAtURL:(NSURL *)sourceURL
toDestinationURL:(NSURL *)destinationURL;
複製程式碼
- 這是在將要呈現新文件時會呼叫, 它接受一個
ColorDocument
物件, 而ColorDocument
又是UIDocument
的子類, 負責儲存和載入色彩檔案, 待會我們就會ColorController
裡預覽和編輯顏色檔案.
- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
didRequestDocumentCreationWithHandler:(void(^)(NSURL *_Nullable urlToImport, UIDocumentBrowserImportMode importMode))importHandler;
複製程式碼
- 在新文件無法匯入時的時候就會呼叫, 比如當我們無權訪問
importHandler
回撥中傳遞的檔案時.
- (void)documentBrowser:(UIDocumentBrowserViewController *)controller
failedToImportDocumentAtURL:(NSURL *)documentURL
error:(NSError * _Nullable)error;
複製程式碼
- 當使用者選擇要在文件瀏覽器中開啟檔案時, 就會呼叫該方法,
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