Sponsor Link
シリーズ記事
Document-based app で PackagedDocument を扱う
[SwiftUI] [Swift] iOS で Packaged Document を使う (その1 Packaged Document を定義して実装する)
[SwiftUI] [Swift] iOS で Packaged Document を使う (その2 Packaged Document をベースにしたモデルを定義して表示する)
ViewModel を作る
前回作成した FileDocument に準拠した PackageDocSwiftUIDocument をモデルとして使用します。
PackageDocSwiftUIDocument を保持するような Observable に準拠したクラスを定義します。
//
// DocumentViewModel.swift
//
// Created by : Tomoaki Yagishita on 2020/11/05
// © 2020 SmallDeskSoftware
//
import Foundation
import SwiftUI
class DocumentViewModel: ObservableObject {
// (1)
@Binding var noteDoc: PackageDocSwiftUIDocument
init(noteDoc: Binding){
self._noteDoc = noteDoc
}
// (2)
var noteString: String {
get {
return noteDoc.noteString
}
set {
noteDoc.noteString = newValue
}
}
// (3)
var image: UIImage? {
get {
if let image = noteDoc.image {
return image
}
return UIImage(systemName: "nosign")
}
set {
noteDoc.image = newValue
}
}
}
- FileDocument プロトコルは、ドキュメントの Binding を提供してくれるので、Binding のまま保持するようにしています。
- 保持している文字列へのアクセサを提供しています。(特に何もしません)
- イメージへのアクセサ定義。ドキュメントが、image を保持していない時には、”nosign” のImage を代わりに返しています。
こうすることで、SwiftUI のビュー側で、nil 判定をなくすことができ、表示にフォーカスすることができるようになります。
View を作る
テキストとイメージを表示するだけですが、テキスト表示ビューの DocumentTextView とイメージ表示ビュー の DocumentImageView に分けてみました。
テキスト表示 DocumentTextView
TextEditor をそのまま Wrap して作りました。
struct DocumentTextView: View {
@Binding var text: String
var body: some View {
TextEditor(text: $text)
.border(Color.gray)
.ignoresSafeArea(.keyboard, edges: .all)
}
}
イメージ表示 DocumentImageView
写真をクリックすると、UIImagePickerController を表示します。
そのために、拙作のライブラリを使っています。ライブラリは、こちら。
SwiftPM で git の URL を入力すれば使い始められます。
SwiftUIImagePickerController が色々としてくれるので、コードとしてはシンプルになりました。
struct DocumentImageView: View {
// (1)
@Binding var image: UIImage?
@State private var metaData:NSDictionary? = nil
@State private var showPhotoPicker = false
var body: some View {
Image(uiImage: image!)
.resizable()
.scaledToFit()
.border(Color.gray)
.onTapGesture {
// (2)
showPhotoPicker.toggle()
}
.fullScreenCover(isPresented: $showPhotoPicker) {
// (3)
SwiftUIImagePickerController(image: $image, metaData: $metaData, showCameraView: $showPhotoPicker)
}
}
}
- SwiftUIImagePickerController で必要とする変数を定義しています。metaData は、選択された写真のメタデータですが、今回は使っていません
- イメージをタップされた時に、SwiftUIImagePickerController を表示します
- SwiftUIImagePickerController を表示します
ContentView
DocumentTextView と DocumentImageView を表示し、ViewModel を保持する ContentView は、以下のようになります。
struct ContentView: View {
@ObservedObject var viewModel: DocumentViewModel
var body: some View {
VStack {
DocumentTextView(text: $viewModel.noteString)
.frame(width: UIScreen.main.bounds.width, height: 200)
DocumentImageView(image: $viewModel.image)
.frame(width: UIScreen.main.bounds.width, height: 200)
}
}
}
# frame の大きさは、適当に決めてます。
App
最後に、DocumentGroup が ContentView に制御を渡す App を説明します。
MVVM で作りたかったので、少し強引に、ViewModel を作って渡しています。
//
// PackageDocSwiftUIApp.swift
//
// Created by : Tomoaki Yagishita on 2020/11/05
// © 2020 SmallDeskSoftware
//
import SwiftUI
@main
struct PackageDocSwiftUIApp: App {
var body: some Scene {
DocumentGroup(newDocument: PackageDocSwiftUIDocument()) { file in
// (1)
let viewModel = DocumentViewModel(noteDoc: file.$document)
// (2)
ContentView(viewModel: viewModel)
}
}
}
- file は、FileDocumentConfiguration<Document> というタイプですが、保持している Document から、ViewModel を作っています
- Document から作成した ViewModel を、ContentView に渡しています。View からは、ViewModel 経由で Model(Document)へアクセスします。
作成したアプリ動作
作成したアプリは以下のように動作します。
まとめ: Package Document を使う iOS アプリの設定
- Document Type, Exported Type IDを設定する
- UTType も定義し、FileDocument#readableContentTypes に設定する
- FileDocument#init で、読み込み用の FileWrapper を設定する
- FileDocument#fileWrapper で、保存用の FileWrapper を設定する
- DocumentGroup は、FileDocumentConfiguration<Document>を渡すので、Document から ViewModel を作って、ドキュメント表示用の View (ContentView) に渡す
- FileDocument は、Document への Binding を提供してくれるので、うまく使う
説明は以上です。 Happy Coding!
Sponsor Link