更新於 2024/10/15閱讀時間約 10 分鐘

Swift 操作檔案的VC - UIDocumentPickerViewController

一樣先來看官方文件

A view controller that provides access to documents or destinations outside your app’s sandbox.

其實就是讓你去讀取檔案App的東西

iphone裡的檔案App

有兩種模式,Don’t copy the document if you can avoid it. 原則上是不需要就不要特別copy一份:

  1. 打開檔案:可以選擇存取原本的檔案或是copy一份來修改,讓你不會動到原本的檔案。
  2. 輸出到指定位置:指定檔案移到目的地,也可以選擇改變原本的檔案,或是copy一份來修改。

Init的方法

也分兩種(四種?)

打開檔案:操作原本/copy的檔案

  • init(forOpeningContentTypes: [UTType])Creates and returns a document picker that can open the types of documents you specify.
  • init(forOpeningContentTypes: [UTType], asCopy: Bool)Creates and returns a document picker that can open or copy the types of documents you specify.
使用這兩個需先 import UniformTypeIdentifiers,不然會有錯誤

輸出到指定位置:操作原本/copy的檔案

  • init(forExporting: [URL])Creates and returns a document picker that can export the types of documents you specify.
  • init(forExporting: [URL], asCopy: Bool)Creates and returns a document picker that can export or copy the types of documents you specify.

下面的範例都是用第一種forOpeningContentTypes來講。

操作原本檔案需要權限

選擇操作原本檔案,開始存取前,需加上 url.startAccessingSecurityScopedResource(),結束操作後url.stopAccessingSecurityScopedResource()

如果操作原本的檔案又沒加上權限,會取不到檔案。

使用init(forOpeningContentTypes: [UTType])瀏覽檔案

// 打開documentPicker 
@IBAction func openFile(_ sender: UIButton) {
let documenPickerVC = UIDocumentPickerViewController(forOpeningContentTypes: [.content])
documenPickerVC.delegate = self
self.present(documenPickerVC, animated: true)
}

❌ 沒有加上權限存取,讀不到檔案。這邊注意一定要用實機測試,模擬器兩種都不需要權限,會讓你以為天下太平= =

extension ViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
do {
for url in urls {
// 直接操作檔案
let filename = url.lastPathComponent
let resourceValue = try url.resourceValues(forKeys: [.fileSizeKey])
filenameLabel.text = "檔案名稱:\(filename)"
fileSizeLabel.text = "檔案大小:\(resourceValue.fileSize ?? 0)"
}
} catch {
print(error.localizedDescription)
}
}
}

取不到檔案內容

The file “ppt檔.pptx” couldn’t be opened because you don’t have permission to view it.

✅正確的寫法

extension ViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
do {
for url in urls {
    let accessing = url.startAccessingSecurityScopedResource()
     defer {
if accessing {
url.stopAccessingSecurityScopedResource( }
}
// ....操作檔案

另外,有打開權限就要關掉,沒關掉的會可能會造成leak,之後app就沒辦法在存取檔案裡的東西,除非重開。所以最佳寫法就是stopAccessingSecurityScopedResource寫在defer {},跑完一定會關掉。

拿到正確的檔案資訊

copy的不需要額外權限

使用init(forOpeningContentTypes: [UTType], asCopy: Bool)copy一份的方式瀏覽檔案

// 打開documentPicker 
@IBAction func openFile(_ sender: UIButton) {
let documenPickerVC = UIDocumentPickerViewController(forOpeningContentTypes: [.content], asCopy: true)
documenPickerVC.delegate = self
self.present(documenPickerVC, animated: true)
}

✅ 直接操作檔案

extension ViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
do {
for url in urls {
// 直接操作檔案
let filename = url.lastPathComponent
let resourceValue = try url.resourceValues(forKeys: [.fileSizeKey])
filenameLabel.text = "檔案名稱:\(filename)"
fileSizeLabel.text = "檔案大小:\(resourceValue.fileSize ?? 0)"
}
} catch {
print(error.localizedDescription)
}
}
}

拿到正確的資訊

我個人偏好跟官方講的一樣,如果沒有特殊需求直接操作原本的檔案就好,不會想要再copy一份,雖然copy方式code比較少😂

UTType

forOpeningContentTypes: [UTType]這個參數,可以讓你限制使用者可以選擇的檔案,不能選擇的會泛灰不能點選,但這東西我覺得極為難用。

因為有被他收編的很輕易就可以寫出來,例如:.jpeg, .png,輕鬆優雅的寫在UTType array裡。

let documenPickerVC = UIDocumentPickerViewController(forOpeningContentTypes: [.jpeg, .png])


但沒被他收編的。例如:微軟word檔、ppt檔。需要自己去找對照表id轉成UTType,非常醜且可怕,而且對照表還莫名的很難搜尋到。

let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType("com.microsoft.powerpoint.ppt")!, UTType("com.microsoft.word.doc")!])


好在iOS14之後他出了一個比較優雅的做法,用fileExtension去判斷type。不過如果使用者亂改副檔名的話,這個判斷結果可能會失準,需自行斟酌。

let acceptTypes = [UTType(filenameExtension: "ppt"), UTType(filenameExtension: "doc")].compactMap{$0}
let documenPickerVC = UIDocumentPickerViewController(forOpeningContentTypes: acceptTypes)



分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.