[SwiftUI] [Swift] iOS で Packaged Document を使う (その2 Packaged Document をベースにしたモデルを定義して表示する)

SwiftUI

パッケージ型のドキュメントを使う方法を説明します。
SwiftUI[SwiftUI] [Swift] iOS で Packaged Document を使う (その1 Packaged Document を定義して実装する)

ViewModel を作る

前回作成した FileDocument に準拠した PackageDocSwiftUIDocument をモデルとして使用します。

PackageDocSwiftUIDocument を保持するような Observable に準拠したクラスを定義します。

ViewModel DocumentViewModel code

//
//  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
        }
    }
}
コード解説
  1. FileDocument プロトコルは、ドキュメントの Binding を提供してくれるので、Binding のまま保持するようにしています。
  2. 保持している文字列へのアクセサを提供しています。(特に何もしません)
  3. イメージへのアクセサ定義。ドキュメントが、image を保持していない時には、"nosign" のImage を代わりに返しています。
    こうすることで、SwiftUI のビュー側で、nil 判定をなくすことができ、表示にフォーカスすることができるようになります。

View を作る

テキストとイメージを表示するだけですが、テキスト表示ビューの DocumentTextView とイメージ表示ビュー の DocumentImageView に分けてみました。

テキスト表示 DocumentTextView

TextEditor をそのまま Wrap して作りました。

DocumentTextView code

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 が色々としてくれるので、コードとしてはシンプルになりました。

DocumentImageView code

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)
            }
    }
}
コード解説
  1. SwiftUIImagePickerController で必要とする変数を定義しています。metaData は、選択された写真のメタデータですが、今回は使っていません
  2. イメージをタップされた時に、SwiftUIImagePickerController を表示します
  3. SwiftUIImagePickerController を表示します

ContentView

DocumentTextView と DocumentImageView を表示し、ViewModel を保持する ContentView は、以下のようになります。

ContentView code

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 code

//
//  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)
        }
    }
}
コード解説
  1. file は、FileDocumentConfiguration<Document> というタイプですが、保持している Document から、ViewModel を作っています
  2. Document から作成した ViewModel を、ContentView に渡しています。View からは、ViewModel 経由で Model(Document)へアクセスします。

作成したアプリ動作

作成したアプリは以下のように動作します。

PackagedDocSwiftUI App

「PackagedDocSwiftUI App」
ソースコードは、プロジェクトファイルも合わせて、こちら

まとめ: Package Document を使う iOS アプリの設定

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!

コメントを残す

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