[SwiftUI]DocumentGroup / FileDocument / ReferenceFileDocument について

SwiftUI2021

     

TAGS:

⌛️ < 1 min.
Document-based App を作るときに使用する DocumentGroup/FileDocument/ReferenceFileDocument の振る舞いをあらためて確認してみました。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterey 12.5 Beta
  • Xcode 14.0 Beta3
  • iOS 16.0 beta

DocumentGroup

DocumentGroup は、SwiftUI で Document-based App を作成するときに使用する View です。
Document-based App のルートのビューとして使用されます。

Apple のドキュメントは、こちら

Document の種類

DocumentGroup は、2つのタイプのドキュメントを処理できるようになっています。
・FileDocument
・ReferenceFileDocument

2つのドキュメントの相違点は、Value-type であるか Reference-type であるかです。

機能面から言うと、Value-type のドキュメントである FileDocument を使用すると UNDO/REDO がサポートされますが、ReferenceFileDocument を使用するときには、自分で UNDO/REDO を実装しなければいけません。

Editor と Viewer

DocumentGroup には、エディタ向け initializer と ビューア向け initializer の2つが用意されています。
例えば、FileDocument 向けには、エディタ向け、ビューア向けとして以下の2つの initializer が用意されています。


// エディタ向け
init(
    newDocument: @autoclosure @escaping () -> Document,
    editor: @escaping (FileDocumentConfiguration) -> Content
)

// ビューア向け
init(
    viewing documentType: Document.Type,
    viewer: @escaping (FileDocumentConfiguration) -> Content
)

DocumentGroup の initializer

DocumentGroup を使うにあたり、気をつけるべき点があります。

エディタ向け initializer の newDocument の引数には、ドキュメントの initializer を与えますが、ユーザーが新規ドキュメントを選んでいるかどうかにかかわらず、常にコールされます。

例えば、ユーザーが既存ドキュメントを開こうとしても、新規ドキュメントの initializer が呼び出され、その後、既存ファイルの情報を持った ReadConfiguration 付きの initializer が呼び出されるという動作になります。

既存ファイルの読み込み

ユーザーが既存ファイルを選択したときには、init(configuration: ReadConfiguration) が呼び出されます。

これは、FileDocument, ReferenceFileDocument のどちらも同じ手順です。

ReadConfiguration についての Apple のドキュメントは、こちら
ReadConfiguration は、contentType: UTType と file: FileWrapper で構成されています。

FileWrapper を使って、ファイルの中身を読み出していくことになります。
つまり、ドキュメントが通常のファイルであれば、configuration.file.regularFileContents とすることで、ファイルのデータが読み出せることになります。

ファイルへの書き出し

ユーザーが、ファイル保存を選択したときに、fileWrapper() が呼び出されます。
この呼び出しは、FileDocument, ReferenceFileDocument のどちらも同じですが、引数と手順が少し異なっているので注意が必要です。

FileDocument の書き出し

FileDocument での書き出しはシンプルです。

fileWrapper(configuration: WriteConfiguration) が呼び出されます。

WriteConfiguration は、contentType: UTType と existingFile: FileWrapper? で構成されています。
セーブしたことのない FileDocument では、existingFile は、nil になっていますので、必要に応じて 自分で FileWrapper を生成する必要があります。

その後、FileWrapper に対して、データを保存することで、ファイルへの書き出しになります。

# ユーザーが指定したファイル名は、SwiftUI 側で管理されているため、書き出し側で配慮する必要はありません。(逆にいうと、知ることはできないようです。)

FileReferenceDocument の書き出し

ReferenceFileDocument での書き出しは、少し複雑になっています。

fileWrapper(snapshot: Snapshot, configuration: WriteConfiguration) が呼び出されます。

引数が追加され 複雑になっている理由は、ドキュメントが Reference-Type であるからです。書き出し作業はバックグラウンドで実行されるため、書き出し途中で変更が入ったときに 不整合が発生してしまうことが予想されます。
そのために、一度、Snapshot と呼ばれるデータを用意し、そのデータを書き出す形になります。

このことが、ReferenceFileDocument 型自体に Snapshot という associatedtype が設定されている理由です。

そのため、ファイル保存のステップが少し増えて以下のような手順になっています。

1. ReferenceFileDocument.snapshot が呼び出される
2. Snapshot を引数として、fileWrapper(snapshot: Snapshot, configuration: WriteConfiguration) が呼び出される

まとめ

DocumentGroup とそこで扱われる FileDocument, ReferenceFileDocument を説明しました。

DocumentGroup とそこで扱われる FileDocument, ReferenceFileDocument
  • DocumentGroup には、Editor 向けと Viewer 向けの initializer が用意されている
  • Document は、FileDocument と ReferenceFileDocument に大別される
  • FileDocument は Value-type のドキュメント
  • ReferenceFileDocument は、Reference-type のドキュメント

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

# SwiftUI2.0 が登場したことで少し古くなってしまいましたが、いまでも 定番本です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です