見出し画像

【じっくりSw1ftUI64】実践編34〜第46章 SwiftUI DocumentGroup シーンの概要

さてと、前回

SwiftUI Chartsの基本

についてはやったので〜〜〜今回は、

SwiftUI DocumentGroup シーン

についてやってく🕺

毎度、オイラの学びなんざ関係ないって人は、

に全部載ってるみたいだからそっちでやればいいんじゃね👀💦
じゃ、今回も早速


じっくり第46章を読んでく👓

概要

SwiftUI DocumentGroupとは何ぞや❓

って解説から入ってんね。どうやら新しく

DocumentApp

ってのを追加しないといけないみたいだから、

これまでのアプリ群が追加後に壊れたら嫌

なので〜〜〜〜

これまでのプロジェクトファイル

を、

てな感じでバックアップで

壊れた時用に、すぐに元に戻せるようにしておいて〜〜〜
(ま、Swiftの設計思想上、壊れることはまずないのだが💦)

早速動かしてみる

動かしてみると、そもそも

今回は、完全に新しいプロジェクトとして選ばないといけない

みたい🤣なので〜〜〜

File >New >Project…の順番でクリック
Document Appを選んでNext

次の図はあくまでもサンプル例だけど

てな感じで、ProjectNameをテキトーにつけて、Next~~

あとは、適当に、プロジェクトフォルダの格納場所を選んでCreateを押せば

てな感じで、新しいプロジェクトファイルが開くので

今回はこいつを使って操作をしてく〜〜〜

さっきCreateの途中でSwiftDataを選んでやったけど、

シーンプロファイル(E46DocumentsGroupAppApp.swift)としては、

import SwiftUI
import SwiftData
import UniformTypeIdentifiers

@main
struct E46DocumentsGroupAppApp: App {
    var body: some Scene {
        DocumentGroup(editing: .itemDocument, migrationPlan: E46DocumentsGroupAppMigrationPlan.self) {
            ContentView()
        }
    }
}

extension UTType {
    static var itemDocument: UTType {
        UTType(importedAs: "com.example.item-document")
    }
}

struct E46DocumentsGroupAppMigrationPlan: SchemaMigrationPlan {
    static var schemas: [VersionedSchema.Type] = [
        E46DocumentsGroupAppVersionedSchema.self,
    ]

    static var stages: [MigrationStage] = [
        // Stages of migration between VersionedSchema, if required.
    ]
}

struct E46DocumentsGroupAppVersionedSchema: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] = [
        Item.self,
    ]
}

で、みた感じは、特にSwiftDataのインポートくらいしか出てきてないね👀
もう一個プロジェクトファイルを用意して、今度は

Storageも外して、IncludeTestのチェックも外して

新規プロジェクトをもう一個作って見比べてみると、、、

ここでポイント①:必要最低限なソースとしては

◾️E46DocumentsGroupApp2App.swift

struct E46DocumentsGroupApp2App: App {
    var body: some Scene {
        DocumentGroup(newDocument: E46DocumentsGroupApp2Document()) { file in
            ContentView(document: file.$document)
        }
    }
}

◾️E46DocumentsGroupApp2Document.swift

import SwiftUI
import UniformTypeIdentifiers

extension UTType {
    static var exampleText: UTType {
        UTType(importedAs: "com.example.plain-text")
    }
}

struct E46DocumentsGroupApp2Document: FileDocument {
    var text: String

    init(text: String = "Hello, world!") {
        self.text = text
    }

    static var readableContentTypes: [UTType] { [.exampleText] }

    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = String(data: data, encoding: .utf8)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        text = string
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = text.data(using: .utf8)!
        return .init(regularFileWithContents: data)
    }
}

◾️ContentView.swift

import SwiftUI

struct ContentView: View {
    @Binding var document: E46DocumentsGroupApp2Document

    var body: some View {
        TextEditor(text: $document.text)
    }
}

#Preview {
    ContentView(document: .constant(E46DocumentsGroupApp2Document()))
}

たったこれだけみたいだね🌱
なので〜〜〜

最初に新規で追加した方のプロジェクトファイルは廃棄〜〜〜

で、本編に戻るけど、

struct E46DocumentsGroupApp2App: App {
    var body: some Scene {
        DocumentGroup(newDocument: E46DocumentsGroupApp2Document()) { file in
            ContentView(document: file.$document)
        }
    }
}

が、ドキュメントグループ内のファイルを

CDMR(Create,Delete,Move、Rename)する際に必要な
インフラストラクチャ

な役割なんだね。で特に、

ContentView(document: file.$document)

が、アプリ画面の操作と裏側のAppシーンの管理を繋ぐ大事な役割を果たしてるみたいなことを書いてんね🧐

で、タイプサポートの宣言が必要

ここはどんなドキュメントタイプかを明確に管理するために必要って言ってんね👀💦

import SwiftUI
import UniformTypeIdentifiers

extension UTType {
    static var exampleText: UTType {
        UTType(importedAs: "com.example.plain-text")
    }
}

struct E46DocumentsGroupApp2Document: FileDocument {
    var text: String

    init(text: String = "Hello, world!") {
        self.text = text
    }

    static var readableContentTypes: [UTType] { [.exampleText] }

    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = String(data: data, encoding: .utf8)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        text = string
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = text.data(using: .utf8)!
        return .init(regularFileWithContents: data)
    }
}

コンテントビューにHello ,Worldを出してるこの部分みたいだね👀

で、App管理者にも紐付けが必要で宣言

        UTType(importedAs: "com.example.plain-text")

って書いてるけど、ここは他のアプリも共通じゃね?👀💦
て感じで

ふーん。そうなんだあ🧐
ベンキョウニナルナア🤖

くらいな感じだし、

次のハンドラーランクも

ふーん。そういう奴が必要なんだね👀🌱

って感じ。

でその後も、

タイプアイデンティファーズでファイルタイプ(要は、ファイルの拡張子)を識別する
みたいなことを書いてるけど、Appleの公式サイトを見ると、

では、すでに非推奨になっていて、

が今の推奨みたいだね👀

うーむ。ここまで来るだけでも、すっげえ今回は

概要を本当にツラツラと書いてる感じで。
長いし、動かしてみないとわからない藁🤣
ような内容を延々書いてる感じ。
退屈で眠くなる🥱

でお次は、ファイル名拡張機能で、

extension UTType {
    static var exampleText: UTType {
        UTType(importedAs: "com.example.plain-text")
    }
}

ここの部分でファイル名管理機能を拡張してますみたいな感じかな🧐

で、独自にカスタマイズされたドキュメントコンテンツを識別するのに、

        UTType(importedAs: "com.example.plain-text")

みたいな部分で、宣言が必要てゆーてんのかしら?

さらに、インポートVSエクスポートみたいなことも見出しで書いていて、

冒頭のリンクから引用すると、

”組み込み型 ( plain.imageなど) が使用される場合、それはインポートされた型識別子と呼ばれます(システムに既に認識されている識別子の範囲からアプリにインポートされるため)。
一方、カスタム型識別子は、アプリ内から生成され、ブラウザーがその型のファイルをアプリに関連付けられていると認識できるようにシステムにエクスポートされるため、エクスポートされた型識別子と呼ばれます。”

って書いてんだけど、

DocumentGroupAppを動かしたことがない人からしたら、
何を言いたいのか読んだだけでは意味がわからない。

で、ここまでがiBooksでゆーと、ここまでで20ページ中の10ページ近くを費やして

👉日本の理系とか自分をエリートとか優秀と勘違いしてるメリトクラシー
=精神年齢が幼稚な子供だけの歳食っただけの大人

でも多いんだけど、

自分が分かってることは、相手も分かってる前提で
いきなり概念だけを展開するのはやめてくれ藁🤣

👉自分は富士山の素晴らしさを熱く広めようと、富士山に登ったことがない人に、富士登山のキツさを話してるようなもの。
=「富士山は難しそうな山だからやっぱり登らないほうがいいな」
で、ほとんどの人がますます登らなくなるようなもの

でここからやっとこ具体的な操作で〜〜〜

まずは、これらのサポート機能を使うには最初にInfo.plistの設定が必要
(なら最初からInfo.plistの操作から入ればいいのに、、、。しかもサポート機能やったんかい藁🤣)

って書いていて、

左上のコイツをクリック
Targetsのコイツをクリック
上図では、青くなってるInfoをクリック
Document Type(1)をクリックして
展開されたTypesにUTTypeがセットされてるかを確認
👉変更がある場合は、自分の設定したいUTType名を入れる
ってことみたいね👀

で、インポートを確認

ここもすでに、建て付けで本どおりになってんね
なんかURLの後が、本の執筆時とはレイアウト変わってるみたいだけど、、、

で、エクスポートの方は

何もなかったので〜〜+をクリックして

てな感じで追加して
てな感じで設定して(identifierは自分のBundleIdentifierを入力)

で、長かったけど、やっとことこれで、コードを書いてく準備も完了
(マジで、ここから操作させながら、必要な概念を都度レクチャで良くないか?なんでこんな法律学みたいなパンデクテン方式なん👀💦🧐)

いよいよコード

◾️E46DocumentsGroupApp2Document.swift

import SwiftUI
import UniformTypeIdentifiers

extension UTType {
    static var exampleText: UTType {
        UTType(importedAs: "com.example.plain-text")
    }
}

struct E46DocumentsGroupApp2Document: FileDocument {
    var text: String

    init(text: String = "Hello, world!") {
        self.text = text
    }

    static var readableContentTypes: [UTType] { [.exampleText] }

    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = String(data: data, encoding: .utf8)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        text = string
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = text.data(using: .utf8)!
        return .init(regularFileWithContents: data)
    }
}

の解説からやっとこ入っていて、ツラツラと読みながら、ここは
分かりやすいようにコードの中に解説をまとめてくと、

import SwiftUI
import UniformTypeIdentifiers

extension UTType {
    static var exampleText: UTType {
        /**
         【解説】  
         新しいUTTypeをexampleTextとして宣言
         */
        UTType(importedAs: "com.example.plain-text")
    }
}
/**
 【解説】 
  すぐ下の構造体は、FileDocumentクラスをベースに作成
 */
struct E46DocumentsGroupApp2Document: FileDocument {
    var text: String
    /**
     【解説】 
      ユーザーリクエストとテキスト文字を制御するイニシャライザ
     */
    init(text: String = "Hello, world!") {
        self.text = text
    }
    /**
     【解説】 
      拡張したUTTypeをここで利用
     */
    static var readableContentTypes: [UTType] { [.exampleText] }
    /**
     【解説】 
      ファイルの存在と読み取り可能かを管理するイニシャライザ
      ReadConfigurationインスタンスは、データフォーマットの中のコンテンツファイル
        👉regularFileContentsプロパティ経由でアクセスする
     */
    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = String(data: data, encoding: .utf8)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        text = string
    }
    /**
     【解説】
       regularFileContentsプロパティ経由でアクセスのエンコードやデコードなどの文字制御のプロパティラッパー
     */
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = text.data(using: .utf8)!
        return .init(regularFileWithContents: data)
    }
}

てな感じのことが書いてんね👀
めっちゃ入り組んでる感💦

で、コンテントビューなんだけど

        TextEditor(text: $document.text)

で、さっきのE46DocumentsGroupApp2Document.swiftから引っ張ってきてるだけやね

import SwiftUI

struct ContentView: View {
        //E46DocumentsGroupApp2Document.swiftとバインディグ
    @Binding var document: E46DocumentsGroupApp2Document

    var body: some View {
        //テキスト形式でE46DocumentsGroupApp2Document.swiftのイニシャライザを利用
        TextEditor(text: $document.text)
    }
}

#Preview {
    ContentView(document: .constant(E46DocumentsGroupApp2Document()))
}

で、ここにナビゲーションを加えて〜〜〜

サンプルを動かしてみると〜〜〜

おお👀

本と同じ動きになった〜〜〜
って、本自体が、DocumentAppのXcode立て付けにナビゲーションタイトルだけをつけてるんだから当たり前
👉逆にこれで動かなかったら驚く藁🤣

今回のコードまとめ(ほぼ、新規作成のまま)

◾️E46DocumentsGroupApp2App.swift

import SwiftUI

@main
struct E46DocumentsGroupApp2App: App {
    var body: some Scene {
        DocumentGroup(newDocument: E46DocumentsGroupApp2Document()) { file in
            ContentView(document: file.$document)
        }
    }
}

◾️E46DocumentsGroupApp2Document.swift

import SwiftUI
import UniformTypeIdentifiers

extension UTType {
    static var exampleText: UTType {
        UTType(importedAs: "com.example.plain-text")
    }
}

struct E46DocumentsGroupApp2Document: FileDocument {
    var text: String

    init(text: String = "Hello, world!") {
        self.text = text
    }

    static var readableContentTypes: [UTType] { [.exampleText] }

    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = String(data: data, encoding: .utf8)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        text = string
    }

    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = text.data(using: .utf8)!
        return .init(regularFileWithContents: data)
    }
}

◾️ContentView.swift

import SwiftUI

struct ContentView: View {
    @Binding var document: E46DocumentsGroupApp2Document

    var body: some View {
        TextEditor(text: $document.text)
            .navigationTitle("ドキュメントを編集")
    }
}

#Preview {
    ContentView(document: .constant(E46DocumentsGroupApp2Document()))
}

Apple公式

さてと、次回は

ドキュメントグループの概要は今回やったので、

第47章 SwiftUI DocumentGroup チュートリアル

で実践編をやってく🧐

記事公開後、

いつもどおり、

でやった操作を〜〜〜
って毎回やってるんだけど、

今回は、以下の理由でなし🕺

  • 次回も含めて、DocumentGroupを追加するInfo.plistなんかを既存のプロジェクトファイルに詰め込んで動くかを検証したい

  • 今回は主に概要だけで、Xcodeが自動生成したコードとほぼ同じなので面白みがない

ではまた次回(多分来週かな)

あとは、

新聞とジムと文藝春秋で今週もアナログで過ごしまする〜〜〜〜

いいなと思ったら応援しよう!