SwiftUI から UIDocumentPickerViewController を呼び出す

概要

SwiftUI から UIDocumentPickerViewController (opens in a new tab) を呼び出す方法を説明します。 具体的には以下を実現します。

  1. ユーザにフォルダーを選択させ、そこにファイルを作成する。
  2. ユーザにファイルを選択させ、その内容を読み取る。

UIDocumentPickerViewController を UIViewControllerRepresentable でラップする

UIDocumentPickerViewController を UIViewControllerRepresentable でラップします。 初期化の引数として、UIDocumentPickerViewController の引数である、openingContentTypesasCopyを受け取ります。

DocumentPickerView.swift
import SwiftUI
import UniformTypeIdentifiers
 
struct DocumentPickerView : UIViewControllerRepresentable {
    let openingContentTypes: [UTType]
    let asCopy: Bool
 
    private var didPickDocumentCallback: ((URL) -> Void)?
 
    init(openingContentTypes: [UTType], asCopy: Bool = false) {
        self.openingContentTypes = openingContentTypes
        self.asCopy = asCopy
    }
 
    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        let documentPickerViewController =  UIDocumentPickerViewController(forOpeningContentTypes: openingContentTypes, asCopy: asCopy)
        documentPickerViewController.delegate = context.coordinator
        return documentPickerViewController
    }
 
    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}
 
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
 
    /// ドキュメントが選択された際のコールバックを指定します
    func didPickDocument(callback: @escaping (URL) -> Void) -> Self {
        var view = self
        view.didPickDocumentCallback = callback
        return view
    }
 
    class Coordinator: NSObject, UIDocumentPickerDelegate {
        var parent: DocumentPickerView
 
        init(_ parent: DocumentPickerView) {
            self.parent = parent
        }
 
        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
            self.parent.didPickDocumentCallback?(url)
        }
    }
}

SwiftUI からの呼び出し方

UserDictionaryView.swift
import SwiftUI
import UniformTypeIdentifiers
 
struct UserDictionaryView: View {
    @State private var showsImportDocumentPicker = false
    @State private var showsExportDocumentPicker = false
 
    var body: some View {
        VStack(spacing: 0) {
            Button("exportToFile", action: {
                showsExportDocumentPicker = true
            })
            Button("importFromFile", action: {
                showsImportDocumentPicker = true
            })
        }
        .sheet(isPresented: $showsExportDocumentPicker) {
            // 1. ユーザにフォルダーを選択させ、そこにファイルを作成する。
            DocumentPickerView(openingContentTypes: [UTType.folder])
                .didPickDocument { directoryURL in
                    guard directoryURL.startAccessingSecurityScopedResource() else { return }
                    defer { directoryURL.stopAccessingSecurityScopedResource() }
                    let newFileURL = directoryURL.appendingPathComponent("fileName.tsv")
                    do {
                        try "a,b,c".write(to: newFileURL, atomically: true, encoding: .utf8)
                    } catch {
                        print("Failed to export")
                    }
                }
        }
        .sheet(isPresented: $showsImportDocumentPicker) {
            // 2. ユーザにファイルを選択させ、その内容を読み取る。
            DocumentPickerView(openingContentTypes: [UTType.text])
            .didPickDocument { fileURL in
                do {
                    guard fileURL.startAccessingSecurityScopedResource() else { return }
                    defer { fileURL.stopAccessingSecurityScopedResource() }
                    let fileContent = try String(contentsOf: fileURL, encoding: .utf8)
                    print(fileContent)
                } catch {
                    print("Failed to import")
                }
            }
        }
    }
}

参考

© 品川アプリ.RSS