メインコンテンツへスキップ
見出し画像

【じっくりSw1ftUI44】実践編14〜第26章 SwiftUI 同時実行性とライフサイクル イベント修飾子

    さてと、前回

    で

    Actor

    については触れたので、今回は

    ライフサイクルイベント

    について触れてく🕺
    これを組み込めるようになると、

    • アプリを起動した瞬間=最初の画面が開いた瞬間に音を出す

    • ボタンを何かタップした瞬間に、効果音を出す、イメージを出す

    みたいなことができるようになるので、

    覚えておいて損はなし
    (てかこの本で書いてることは、全てエッセンス=素材なのでどれも触れておいて損はないが藁🤣)

    今回も、オイラの学びなんざ必要なしって人は、

    にサンプルコードなんかは載ってそうだからそっち見たらいいんじゃね👀💦
    さてと、じゃ早速〜〜〜

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

    と、前回好評だったみたいなので、

    結論先出し(まとめ)

    1. SwiftUI は、実行中のアプリでライフサイクルの変更が発生した場合にアクションを実行できるように設計された修飾子のコレクションを提供。

    2. onAppear ()およびonDisapear:ユーザー インターフェイス レイアウト内でビューが表示されるとき、またはビューから消えるときにアクションを実行。

    3. onChange() 修飾子は、プロパティに割り当てられた値が変更されるたびにタスクを実行する場合に便利。

    4. ScenePhase 環境プロパティをonChange()修飾子とともに使用すると、アプリはシーンの状態がいつ変化したかを識別できる。

    5. これは、アプリがフォアグラウンド モードとバックグラウンド モードの間でいつ移行するかを知る必要がある場合に特に役立つ。

    6. 非同期タスクは、 task()修飾子を使用してビューを作成するときに起動できる。

    ってのを簡単に頭に入れといて〜〜〜
    実際の詳細を見ていく🕺

    概要では、

    • appear

    • disappear

    • change

    • active

    • inactive

    なんかについてサラッと書いてんね👀こんなもんは論より証拠、案ずるより産むが易しなので〜〜〜〜

    早速コードを書いてく

    import SwiftUI
    
    struct Essentials26ContentsView: View {
        var body: some View {
                TabView{
                    TabView{
                        E26GrapeView()
                            .tabItem {
                                Image(systemName: "applescript.fill")
                                Text("葡萄")
                            }
                        E26AppleView()
                            .tabItem {
                                Image(systemName: "apple.logo")
                                Text("林檎")
                            }
                        E26OrangeView()
                            .tabItem {
                                Image(systemName: "applescript")
                                Text("蜜柑")
                            }
                    }
                }
        }
    }
    
    struct E26AppleView:View {
        var body: some View {
            Text("🍎")
                .font(.largeTitle)
        }
    }
    struct E26OrangeView:View {
        var body: some View {
            Text("🍊")
                .font(.largeTitle)
        }
    }
    struct E26GrapeView:View {
        var body: some View {
            Text("🍇")
                .font(.largeTitle)
        }
    }
    #Preview {
        Essentials26ContentsView()
    }

    まずはてな感じで、今回用のビューを作って〜〜〜

    画像
    てな感じ👀

    表示と非表示イベント

    import SwiftUI
    
    struct Essentials26ContentsView: View {
        var body: some View {
                TabView{
                    TabView{
                        E26GrapeView()
                            .tabItem {
                                Image(systemName: "applescript.fill")
                                Text("葡萄")
                            }
                        E26AppleView()
                            .tabItem {
                                Image(systemName: "apple.logo")
                                Text("林檎")
                            }
                        E26OrangeView()
                            .tabItem {
                                Image(systemName: "applescript")
                                Text("蜜柑")
                            }
                    }
                }
        }
    }
    
    struct E26AppleView:View {
        var body: some View {
            Text("🍎")
                .font(.largeTitle)
                .onAppear(perform: {
                    print("林檎が表示された")
                })
                .onDisappear(perform: {
                    print("林檎が非表示になった")
                })
        }
    }
    struct E26OrangeView:View {
        var body: some View {
            Text("🍊")
                .font(.largeTitle)
                .onAppear(perform: {
                    print("蜜柑が表示された")
                })
                .onDisappear(perform: {
                    print("蜜柑が非表示になった")
                })
        }
    }
    struct E26GrapeView:View {
        var body: some View {
            Text("🍇")
                .font(.largeTitle)
                .onAppear(perform: {
                    print("葡萄が表示された")
                })
                .onDisappear(perform: {
                    print("葡萄が非表示になった")
                })
        }
    }
    #Preview {
        Essentials26ContentsView()
    }
    

    てな感じのコードで、タブをタップすると〜〜〜

    画像
    てな感じで、どれがタップされて表示・非表示になったかがわかる👀💦

    変更イベント

    ちょっとコードがさっきまでだとわかりにくいので〜〜〜

    import SwiftUI
    
    struct Essentials26ContentsView: View {
        var body: some View {
                TabView{
                    TabView{
                        E26Fruits1InputView()
                            .tabItem {
                                Image(systemName: "applescript.fill")
                                Text("果物名①")
                            }
                        E26Fruits2InputView()
                            .tabItem {
                                Image(systemName: "apple.logo")
                                Text("果物名②")
                            }
                        E26Fruits3InputView()
                            .tabItem {
                                Image(systemName: "applescript")
                                Text("果物名③")
                            }
                    }
                }
        }
    }
    
    struct E26Fruits1InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名①を入力", text: $text)
                    .background(Color.pink)
                    .padding()
                    .onChange(of: text){
                        print("果物名①が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名①が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名①が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits2InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名②を入力", text: $text)
                    .background(Color.orange)
                    .padding()
                    .onChange(of: text){
                        print("果物名②が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名②が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名②が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits3InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名③を入力", text: $text)
                    .background(Color.purple)
                    .padding()
                    .onChange(of: text){
                        print("果物名③が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名③が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名③が非表示になった")
                    })
            }
        }
    }
    #Preview {
        Essentials26ContentsView()
    }
    

    てな感じで変更して〜〜〜

    画像
    入力前
    画像
    入力中
    画像
    てな感じで文字を入力する度に、変更をキャッチしてプリント〜〜〜
    画像
    入力後
    画像
    なんてお遊びをしても
    画像
    てなこともできちゃう🕺

    ここでポイント①タブビューにビューを呼び出す場合

    struct E26Fruits1InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名①を入力", text: $text)
                    .background(Color.pink)
                    .padding()
                    .onChange(of: text){
                        print("果物名①が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名①が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名①が非表示になった")
                    })
            }
        }
    }

    ここでVStackでビューを囲んでるんだけど、VStackを外すと、、、

    画像
    てな感じでビューごとにタブが出現して、
    想定どおりの動きと異なる結果になるかも

    だから気をつけてね〜〜〜

    ここでポイント②せっかちな即断即決さんでよく勘違いする人が出てくるんだけど、

    だからVStackを必ず入れろ!!!!

    なんてオイラは一言もゆーてないからね。

    • 同じタブビューの中で入力後の文字を表現したい👉VStackなんかで囲めばいい

    • 違うタブビューで入力後の文字を表現したい👉VStackなんかで囲まなければいい

    👉要求に応じてどうしたいかとか、どんな文字を入力する可能性があるか
    で臨機応変に使い分ければいい
    =正解とかこーじゃないといけないなんてことはない

    ってだけの話だからね👀💦

    画像
    みたいな長文を入れたいのであれば、どーするか?

    で組み込むビューの順番なんかも変える必要は出てくるからね👀

    ま、自分が作りたいアプリに合わせて色々試してみて、
    最適解=ベストプラクティスを自分で発見してみてね〜〜〜

    繰り返しになるけど、

    アプリにこーじゃないといけないなんてモノは一切ないからね🕺
    👉バグを起こさずに、みんなが使いやすく、直観的なモノであればOK

    だし、そこに

    使う人が飽きが来ず、ワクワクするもの
    ってエッセンスが加わればサイコー

    ってだけの話だからね👀
    (よくそこを勘違いして、似たり寄ったり在り来たりなアプリしか作れない企業さんなんかも多い)

    さてと、本題に戻って〜〜〜

    シーン変更モディファイア

    とまあ、本の流れだといきなり、このマガジン用のプロジェクトファイルを作った時に建て付けで出来上がってた

    SwiftUICatalogforiOS17App.swiftファイル

    の操作をここまでであまり説明もしてないのに、それ用のプロジェクトファイルを作ってぶち込んできちゃってんだけど、これをいきなり見ただけでは、多分、

    さっきまでのビューファイルに書こうとして戸惑う人も出てくると思うので〜〜〜

    画像
    オイラのファイルだと左上のここを開いて〜〜〜
    import SwiftUI
    import SwiftData
    
    @main
    struct SwiftUICatalogforiOS17App: App {
        //じっくり第26章で追加
        @Environment(\.scenePhase) private var scenePhase
        
        var sharedModelContainer: ModelContainer = {
            let schema = Schema([
                Item.self,
            ])
            let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
    
            do {
                return try ModelContainer(for: schema, configurations: [modelConfiguration])
            } catch {
                fatalError("Could not create ModelContainer: \(error)")
            }
        }()
    
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
            .modelContainer(sharedModelContainer)
            //じっくり第26章で追加
            .onChange(of: scenePhase){
                switch scenePhase {
                case .background:
                    print("バックグラウンドで実行")
                case .inactive:
                    print("インアクティブになった")
                case .active:
                    print("アクティブになった")
                default:
                    print("識別不能なシーンで実行")
                }
            }
        }
    }

    てな感じでコードを追加しちゃう
    ってやらないと実は動かないんだよね〜〜〜

    ここでポイント③見分けるヒント

    @main
    struct SwiftUICatalogforiOS17App: App {

    ここなんだよね〜〜〜

    「@mainって書いてっし、プロトコルにAppを使ってる
    👉これってアプリ全体を制御するコードじゃん
    =SceneModifierを組み込むアプリの大元のファイルにここのコードは追加しろってことね〜〜〜」
    って感じでやり慣れてる人は一瞬で当たりがつくんだけど、初めて触れる人とかSwiftUIでの開発にまだ馴染んでない人は、

    これはなんだ?なんでコードを本どおりに打ち込んでいるのに訳のわからないエラーが出てくるんだ🧐

    ってなると思うので〜〜〜

    ここでポイント④各イベントの役割

    • active – シーンは前景にあり、表示され、ユーザーの操作に応答。

    • inactive – シーンは前景にあり、ユーザーに表示されるが、インタラクティブではない。

    • background– シーンはユーザーには見えない。

    ここでポイント⑤プロトコル

    今回やっとこSyntax編でちょい出たプロトコルが

    :App

    って感じで出てきてるし、ここの以前紹介した

    って本なんかでプロトコルを詳細に説明してたりすんだけど、

    実は、プロトコル自体はあんまり独自で作ることがないし、作る必要がない

    (理由)

    1. 公開されてるプロトコル自体でめちゃくちゃ数があって、それを使う方が安全

    2. 独自プロトコルを組んじゃうと、Swiftのバージョンが上がった時に、コードの書き方とかが変わったら、アプリ全体に影響を与えてしまう

    3. そのバージョンに合わせてAppleが最適なプロトコルを作ってくれているので、わざわざ自分でプロトコルを作っちゃうと後から涙目

    4. 別に自分が作りたいアプリを作りたいだけで、こんなすごいプロトコルをオイラは組めるんだみたいな研究職とかではないので、どーでもいい

    👉できなくはないけど、後々の改修作業を考えてやらないの極み。
    (他のプログラミング言語全般で言える作法だけどね。
    ま、VBAなんかでも素人とか駆け出しほど難しいことをやろうとして手を出すんだけど、必要なファイルを壊したりして後から涙目になってるし。)

    独自のプロトコルなんか組む暇があったら、
    公開されてるプロトコルを組み合わせてどうするかを考えてる方が安全
    =むやみに独自のプロトコルは作るな!!!

    てだけの話。さてとでは本題に戻って〜〜〜

    環境イベントを使ってみよう

    import SwiftUI
    
    struct Essentials26ContentsView: View {
        var body: some View {
                TabView{
                    TabView{
                        E26Fruits1InputView()
                            .tabItem {
                                Image(systemName: "applescript.fill")
                                Text("果物名①")
                            }
                        E26Fruits2InputView()
                            .tabItem {
                                Image(systemName: "apple.logo")
                                Text("果物名②")
                            }
                        E26Fruits3InputView()
                            .tabItem {
                                Image(systemName: "applescript")
                                Text("果物名③")
                            }
                        E26EnvironmentView()
                            .tabItem {
                                Image(systemName: "zzz")
                                Text("Environment")
                            }
                    }
                }
        }
    }
    
    struct E26Fruits1InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名①を入力", text: $text)
                    .background(Color.pink)
                    .padding()
                    .onChange(of: text){
                        print("果物名①が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名①が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名①が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits2InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名②を入力", text: $text)
                    .background(Color.orange)
                    .padding()
                    .onChange(of: text){
                        print("果物名②が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名②が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名②が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits3InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名③を入力", text: $text)
                    .background(Color.purple)
                    .padding()
                    .onChange(of: text){
                        print("果物名③が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名③が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名③が非表示になった")
                    })
            }
        }
    }
    #Preview {
        Essentials26ContentsView()
    }
    
    struct E26EnvironmentView: View {
        @State private var kanban = "美味しい果物屋さん"
    
        var body: some View {
            Text(kanban)
                .font(.largeTitle)
                .onAppear(perform: {
                    print("表示された")
                })
                .onDisappear(perform: {
                    print("非表示になった")
                })
                .task(priority: .background) {
                    kanban = await changeKanban()
                }
        }
        func changeKanban() async -> String{
            sleep(3)
            return "同時完了"
        }
    }

    てな感じで、同時実行用のビューを追加して〜〜〜

    画像
    表示直後〜〜〜
    画像
    同時実行完了🕺

    お👀前回まででコンパイラにしか出てこなかった状態表示が、

    変数に代入するだけで、ビューに表示されてんね👀
    こいつは使える!!!!

    以上🕺

    今回のコードまとめ

    ◾️Essentials26.swift

    import SwiftUI
    
    struct Essentials26ContentsView: View {
        var body: some View {
                TabView{
                    TabView{
                        E26Fruits1InputView()
                            .tabItem {
                                Image(systemName: "applescript.fill")
                                Text("果物名①")
                            }
                        E26Fruits2InputView()
                            .tabItem {
                                Image(systemName: "apple.logo")
                                Text("果物名②")
                            }
                        E26Fruits3InputView()
                            .tabItem {
                                Image(systemName: "applescript")
                                Text("果物名③")
                            }
                        E26EnvironmentView()
                            .tabItem {
                                Image(systemName: "zzz")
                                Text("Environment")
                            }
                    }
                }
        }
    }
    
    struct E26Fruits1InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名①を入力", text: $text)
                    .background(Color.pink)
                    .padding()
                    .onChange(of: text){
                        print("果物名①が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名①が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名①が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits2InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名②を入力", text: $text)
                    .background(Color.orange)
                    .padding()
                    .onChange(of: text){
                        print("果物名②が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名②が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名②が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits3InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名③を入力", text: $text)
                    .background(Color.purple)
                    .padding()
                    .onChange(of: text){
                        print("果物名③が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名③が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名③が非表示になった")
                    })
            }
        }
    }
    #Preview {
        Essentials26ContentsView()
    }
    
    struct E26EnvironmentView: View {
        @State private var kanban = "美味しい果物屋さん"
    
        var body: some View {
            Text(kanban)
                .font(.largeTitle)
                .onAppear(perform: {
                    print("表示された")
                })
                .onDisappear(perform: {
                    print("非表示になった")
                })
                .task(priority: .background) {
                    kanban = await changeKanban()
                }
        }
        func changeKanban() async -> String{
            sleep(3)
            return "同時完了"
        }
    }
    

    ◾️SwiftUICatalogforiOS17App.swift

    import SwiftUI
    import SwiftData
    
    @main
    struct SwiftUICatalogforiOS17App: App {
        //じっくり第26章で追加
        @Environment(\.scenePhase) private var scenePhase
        
        var sharedModelContainer: ModelContainer = {
            let schema = Schema([
                Item.self,
            ])
            let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
    
            do {
                return try ModelContainer(for: schema, configurations: [modelConfiguration])
            } catch {
                fatalError("Could not create ModelContainer: \(error)")
            }
        }()
    
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
            .modelContainer(sharedModelContainer)
            //じっくり第26章で追加
            .onChange(of: scenePhase){
                switch scenePhase {
                case .background:
                    print("バックグラウンドで実行")
                case .inactive:
                    print("インアクティブになった")
                case .active:
                    print("アクティブになった")
                default:
                    print("識別不能なシーンで実行")
                }
            }
        }
    }

    Apple公式

    さてと次回は、

    第27章 SwiftUI の監視可能なオブジェクトと環境オブジェクト – チュートリアル

    について見てく🕺
    いよいよ実践編も佳境に入ってきたね〜〜〜🧐

    記事公開後

    いつもどおり、

    でやった操作を〜〜〜

    画像
    タブもしっかり2段で出てきてんね👀
    👉ビューを呼び出す方が安全
    これが嫌なら自分で調整してね〜〜〜
    画像
    てな
    画像
    てな感じで
    画像
    ホイ、完了🕺

    サンプルコード

    ◾️Essentials26.swift

    import SwiftUI
    import WebKit
    
    //タイトル
    let essentialsChapter26NavigationTitle = "第26章"
    let essentialsChapter26Title = "第26章 SwiftUI 同時実行性とライフサイクル イベント修飾子"
    let essentialsChapter26SubTitle = "第1節 SwiftUI 同時実行性とライフサイクル イベント修飾子"
    
    //コード
    let codeEssentials26 = """
    struct Essentials26ContentsView: View {
        var body: some View {
                TabView{
                    TabView{
                        E26Fruits1InputView()
                            .tabItem {
                                Image(systemName: "applescript.fill")
                                Text("果物名①")
                            }
                        E26Fruits2InputView()
                            .tabItem {
                                Image(systemName: "apple.logo")
                                Text("果物名②")
                            }
                        E26Fruits3InputView()
                            .tabItem {
                                Image(systemName: "applescript")
                                Text("果物名③")
                            }
                        E26EnvironmentView()
                            .tabItem {
                                Image(systemName: "zzz")
                                Text("Environment")
                            }
                    }
                }
        }
    }
    
    struct E26Fruits1InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名①を入力", text: $text)
                    .background(Color.pink)
                    .padding()
                    .onChange(of: text){
                        print("果物名①が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名①が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名①が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits2InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名②を入力", text: $text)
                    .background(Color.orange)
                    .padding()
                    .onChange(of: text){
                        print("果物名②が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名②が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名②が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits3InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名③を入力", text: $text)
                    .background(Color.purple)
                    .padding()
                    .onChange(of: text){
                        print("果物名③が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名③が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名③が非表示になった")
                    })
            }
        }
    }
    #Preview {
        Essentials26ContentsView()
    }
    
    struct E26EnvironmentView: View {
        @State private var kanban = "美味しい果物屋さん"
    
        var body: some View {
            Text(kanban)
                .font(.largeTitle)
                .onAppear(perform: {
                    print("表示された")
                })
                .onDisappear(perform: {
                    print("非表示になった")
                })
                .task(priority: .background) {
                    kanban = await changeKanban()
                }
        }
        func changeKanban() async -> String{
            sleep(3)
            return "実行完了"
        }
    }
    """
    
    //ポイント
    let pointEssentials26 = """
    ◾️ここでポイント①タブビューにビューを呼び出す場合
    ここでVStackでビューを囲んでるんだけど、VStackを外すと、、、
    てな感じでビューごとにタブが出現して、
    想定どおりの動きと異なる結果になるかも
    だから気をつけてね〜〜〜
    ◾️ここでポイント②せっかちな即断即決さんでよく勘違いする人が出てくるんだけど、
    だからVStackを必ず入れろ!!!!
    なんてオイラは一言もゆーてないからね。
    ・同じタブビューの中で入力後の文字を表現したい👉VStackなんかで囲めばいい
    ・違うタブビューで入力後の文字を表現したい👉VStackなんかで囲まなければいい
    👉要求に応じてどうしたいかとか、どんな文字を入力する可能性があるか
    で臨機応変に使い分ければいい
    =正解とかこーじゃないといけないなんてことはない
    ってだけの話だからね👀💦
    組み込むビューの順番なんかも変える必要は出てくるからね👀
    ま、自分が作りたいアプリに合わせて色々試してみて、
    最適解=ベストプラクティスを自分で発見してみてね〜〜〜
    繰り返しになるけど、
    アプリにこーじゃないといけないなんてモノは一切ないからね🕺
    👉バグを起こさずに、みんなが使いやすく、直観的なモノであればOK
    だし、そこに
    使う人が飽きが来ず、ワクワクするものってエッセンスが加わればサイコー
    ってだけの話だからね👀
    (よくそこを勘違いして、似たり寄ったり在り来たりなアプリしか作れない企業さんなんかも多い)
    ◾️ここでポイント③見分けるヒント
    @main
    struct SwiftUICatalogforiOS17App: App {
    ここなんだよね〜〜〜
    「@mainって書いてっし、プロトコルにAppを使ってる
    👉これってアプリ全体を制御するコードじゃん
    =SceneModifierを組み込むアプリの大元のファイルにここのコードは追加しろってことね〜〜〜」
    って感じでやり慣れてる人は一瞬で当たりがつくんだけど、初めて触れる人とかSwiftUIでの開発にまだ馴染んでない人は、
    これはなんだ?なんでコードを本どおりに打ち込んでいるのに訳のわからないエラーが出てくるんだ🧐
    ってなると思うので〜〜〜
    ◾️ここでポイント④各イベントの役割
    ・active – シーンは前景にあり、表示され、ユーザーの操作に応答。
    ・inactive – シーンは前景にあり、ユーザーに表示されるが、インタラクティブではない。
    ・background– シーンはユーザーには見えない。
    ◾️ここでポイント⑤プロトコル
    今回やっとこSyntax編でちょい出たプロトコルが
    :App
    って感じで出てきてるし、ここの以前紹介した本なんかでプロトコルを詳細に説明してたりすんだけど、
    実は、プロトコル自体はあんまり独自で作ることがないし、作る必要がない
    (理由)
    ・公開されてるプロトコル自体でめちゃくちゃ数があって、それを使う方が安全
    ・独自プロトコルを組んじゃうと、Swiftのバージョンが上がった時に、コードの書き方とかが変わったら、アプリ全体に影響を与えてしまう
    ・そのバージョンに合わせてAppleが最適なプロトコルを作ってくれているので、わざわざ自分でプロトコルを作っちゃうと後から涙目
    ・別に自分が作りたいアプリを作りたいだけで、こんなすごいプロトコルをオイラは組めるんだみたいな研究職とかではないので、どーでもいい
    👉できなくはないけど、後々の改修作業を考えてやらないの極み。
    (他のプログラミング言語全般で言える作法だけどね。
    ま、VBAなんかでも素人とか駆け出しほど難しいことをやろうとして手を出すんだけど、必要なファイルを壊したりして後から涙目になってるし。)
    独自のプロトコルなんか組む暇があったら、公開されてるプロトコルを組み合わせてどうするかを考えてる方が安全=むやみに独自のプロトコルは作るな!!!
    てだけの話。
    """
    //URL
    let urlEssentials26 = "https://note.com/m_kakudo/n/nb0b250a2ba03"
    
    //ビュー管理構造体
    struct ListiOSApp17DevelopmentEssentialsCh26: Identifiable {
        var id: Int
        var title: String
        var view: ViewEnumiOSApp17DevelopmentEssentialsCh26
    }
    //遷移先の画面を格納する列挙型
    enum ViewEnumiOSApp17DevelopmentEssentialsCh26{
        case Sec1
    }
    //各項目に表示するリスト項目
    let dataiOSApp17DevelopmentEssentialsCh26: [ListiOSApp17DevelopmentEssentialsCh26] = [
        ListiOSApp17DevelopmentEssentialsCh26(id: 1, title: essentialsChapter26SubTitle, view: .Sec1),
    ]
    struct iOSApp17DevelopmentEssentialsCh26: View {
        var body: some View {
            VStack {
                Divider()
                List (dataiOSApp17DevelopmentEssentialsCh26) { data in
                    self.containedViewiOSApp17DevelopmentEssentialsCh26(dataiOSApp17DevelopmentEssentialsCh26: data)
                }
                .edgesIgnoringSafeArea([.bottom])
            }
            .navigationTitle(essentialsChapter26NavigationTitle)
            .navigationBarTitleDisplayMode(.inline)
        }
        //タップ後に遷移先へ遷移させる関数
        func containedViewiOSApp17DevelopmentEssentialsCh26(dataiOSApp17DevelopmentEssentialsCh26: ListiOSApp17DevelopmentEssentialsCh26) -> AnyView {
            switch dataiOSApp17DevelopmentEssentialsCh26.view {
            case .Sec1:
                return AnyView(NavigationLink (destination: Essentials26()) {
                    Text(dataiOSApp17DevelopmentEssentialsCh26.title)
                })
            }
        }
    }
    #Preview {
        iOSApp17DevelopmentEssentialsCh26()
    }
    
    struct Essentials26: View {
        var body: some View {
            VStack{
                TabView {
                    Essentials26ContentsView()
                        .tabItem {
                            Image(systemName: contentsImageTab)
                            Text(contentsTextTab)
                        }
                    Essentials26Code()
                        .tabItem {
                            Image(systemName: codeImageTab)
                            Text(codeTextTab)
                        }
                    Essentials26Points()
                        .tabItem {
                            Image(systemName: pointImageTab)
                            Text(pointTextTab)
                        }
                    Essentials26WEB()
                        .tabItem {
                            Image(systemName: webImageTab)
                            Text(webTextTab)
                        }
                }
            }
        }
    }
    #Preview {
        Essentials26()
    }
    
    struct Essentials26Code: View {
        var body: some View {
            ScrollView{
                Text(codeEssentials26)
            }
        }
    }
    #Preview {
        Essentials26Code()
    }
    struct Essentials26Points: View {
        var body: some View {
            ScrollView{
                Text(pointEssentials26)
            }
        }
    }
    #Preview {
        Essentials26Points()
    }
    struct Essentials26WebView: UIViewRepresentable {
        let searchURL: URL
        func makeUIView(context: Context) -> WKWebView {
            let view = WKWebView()
            let request = URLRequest(url: searchURL)
            view.load(request)
            return view
        }
        func updateUIView(_ uiView: WKWebView, context: Context) {
            
        }
    }
    struct Essentials26WEB: View {
        private var url:URL = URL(string: urlEssentials26)!
        var body: some View {Essentials26WebView(searchURL: url)
        }
    }
    #Preview {
        Essentials26WEB()
    }
    
    struct Essentials26ContentsView: View {
        var body: some View {
                TabView{
                    TabView{
                        E26Fruits1InputView()
                            .tabItem {
                                Image(systemName: "applescript.fill")
                                Text("果物名①")
                            }
                        E26Fruits2InputView()
                            .tabItem {
                                Image(systemName: "apple.logo")
                                Text("果物名②")
                            }
                        E26Fruits3InputView()
                            .tabItem {
                                Image(systemName: "applescript")
                                Text("果物名③")
                            }
                        E26EnvironmentView()
                            .tabItem {
                                Image(systemName: "zzz")
                                Text("Environment")
                            }
                    }
                }
        }
    }
    
    struct E26Fruits1InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名①を入力", text: $text)
                    .background(Color.pink)
                    .padding()
                    .onChange(of: text){
                        print("果物名①が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名①が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名①が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits2InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名②を入力", text: $text)
                    .background(Color.orange)
                    .padding()
                    .onChange(of: text){
                        print("果物名②が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名②が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名②が非表示になった")
                    })
            }
        }
    }
    struct E26Fruits3InputView:View {
        @State private var text:String = ""
        var body: some View {
            VStack{
                TextField("果物名③を入力", text: $text)
                    .background(Color.purple)
                    .padding()
                    .onChange(of: text){
                        print("果物名③が変更された")
                    }
                Text(text)
                    .font(.largeTitle)
                    .onAppear(perform: {
                        print("果物名③が表示された")
                    })
                    .onDisappear(perform: {
                        print("果物名③が非表示になった")
                    })
            }
        }
    }
    #Preview {
        Essentials26ContentsView()
    }
    
    struct E26EnvironmentView: View {
        @State private var kanban = "美味しい果物屋さん"
    
        var body: some View {
            Text(kanban)
                .font(.largeTitle)
                .onAppear(perform: {
                    print("表示された")
                })
                .onDisappear(perform: {
                    print("非表示になった")
                })
                .task(priority: .background) {
                    kanban = await changeKanban()
                }
        }
        func changeKanban() async -> String{
            sleep(3)
            return "実行完了"
        }
    }
    
    

    ◾️EssentialsMenu.swift

    //フレームワーク
    import SwiftUI
    import WebKit
    
    //ビュー管理構造体
    struct ListiOSApp17DevelopmentEssentials: Identifiable {
        var id: Int
        var title: String
        var view: ViewEnumiOSApp17DevelopmentEssentials
    }
    //遷移先の画面を格納する列挙型
    enum ViewEnumiOSApp17DevelopmentEssentials {
        case Ch1
        //じっくり13で追加
        case Ch2
        //じっくり14で追加
        case Ch3
        //じっくり15で追加
        case Ch4
        //じっくり16で追加
        case Ch5
        //じっくり17で追加
        case Ch6
        //じっくり18で追加
        case Ch7
        //じっくり19で追加
        case Ch8
        //じっくり20、21で追加
        case Ch9
        //じっくり22、23で追加
        case Ch10
        //じっくり24で追加
        case Ch11
        //じっくり25で追加
        case Ch12
        //じっくり26で追加
        case Ch13
        //じっくり27,28で追加
        case Ch14
        //じっくり29で追加
        case Ch15
        //じっくり31で追加
        case Ch16
        //じっくり32で追加
        case Ch17
        //じっくり33で追加
        case Ch18
        //じっくり34で追加
        case Ch19
        //じっくり35で追加
        case Ch20
        //じっくり36で追加
        case Ch21
        //じっくり37で追加
        case Ch22
        //じっくり40で追加
        case Ch23
        //じっくり41で追加
        case Ch24
        //じっくり43で追加
        case Ch25
        //じっくり44で追加
        case Ch26
    }
    //各項目に表示する文字列
    let dataiOSApp17DevelopmentEssentials: [ListiOSApp17DevelopmentEssentials] = [
        ListiOSApp17DevelopmentEssentials(id: 1, title: essentialsChapter1Title, view: .Ch1),
        //じっくり13で追加
        ListiOSApp17DevelopmentEssentials(id: 2, title: essentialsChapter2Title, view: .Ch2),
        //じっくり13で追加
        ListiOSApp17DevelopmentEssentials(id: 3, title: essentialsChapter3Title, view: .Ch3),
        //じっくり15で追加
        ListiOSApp17DevelopmentEssentials(id: 4, title: essentialsChapter4Title, view: .Ch4),
        //じっくり16で追加
        ListiOSApp17DevelopmentEssentials(id: 5, title: essentialsChapter5Title, view: .Ch5),
        //じっくり17で追加
        ListiOSApp17DevelopmentEssentials(id: 6, title: essentialsChapter6Title, view: .Ch6),
        //じっくり18で追加
        ListiOSApp17DevelopmentEssentials(id: 7, title: essentialsChapter7Title, view: .Ch7),
        //じっくり19で追加
        ListiOSApp17DevelopmentEssentials(id: 8, title: essentialsChapter8Title, view: .Ch8),
        //じっくり20、21で追加
        ListiOSApp17DevelopmentEssentials(id: 9, title: essentialsChapter9Title, view: .Ch9),
        //じっくり22、23で追加
        ListiOSApp17DevelopmentEssentials(id: 10, title: essentialsChapter10Title, view: .Ch10),
        //じっくり24で追加
        ListiOSApp17DevelopmentEssentials(id: 11, title: essentialsChapter11Title, view: .Ch11),
        //じっくり25で追加
        ListiOSApp17DevelopmentEssentials(id: 12, title: essentialsChapter12Title, view: .Ch12),
        //じっくり26で追加
        ListiOSApp17DevelopmentEssentials(id: 13, title: essentialsChapter13Title, view: .Ch13),
        //じっくり27,28で追加
        ListiOSApp17DevelopmentEssentials(id: 14, title: essentialsChapter14Title, view: .Ch14),
        //じっくり29で追加
        ListiOSApp17DevelopmentEssentials(id: 15, title: essentialsChapter15Title, view: .Ch15),
        //じっくり31で追加
        ListiOSApp17DevelopmentEssentials(id: 16, title: essentialsChapter16Title, view: .Ch16),
        //じっくり32で追加
        ListiOSApp17DevelopmentEssentials(id: 17, title: essentialsChapter17Title, view: .Ch17),
        //じっくり33で追加
        ListiOSApp17DevelopmentEssentials(id: 18, title: essentialsChapter18Title, view: .Ch18),
        //じっくり34で追加
        ListiOSApp17DevelopmentEssentials(id: 19, title: essentialsChapter19Title, view: .Ch19),
        //じっくり35で追加
        ListiOSApp17DevelopmentEssentials(id: 20, title: essentialsChapter20Title, view: .Ch20),
        //じっくり36で追加
        ListiOSApp17DevelopmentEssentials(id: 21, title: essentialsChapter21Title, view: .Ch21),
        //じっくり37で追加
        ListiOSApp17DevelopmentEssentials(id: 22, title: essentialsChapter22Title, view: .Ch22),
        //じっくり40で追加
        ListiOSApp17DevelopmentEssentials(id: 23, title: essentialsChapter23Title, view: .Ch23),
        //じっくり41で追加
        ListiOSApp17DevelopmentEssentials(id: 24, title: essentialsChapter24Title, view: .Ch24),
        //じっくり43で追加
        ListiOSApp17DevelopmentEssentials(id: 25, title: essentialsChapter25Title, view: .Ch25),
        //じっくり44で追加
        ListiOSApp17DevelopmentEssentials(id: 26, title: essentialsChapter26Title, view: .Ch26),
    ]
    
    struct iOSApp17DevelopmentEssentials: View {
        var body: some View {
            VStack {
                Divider()
                List (dataiOSApp17DevelopmentEssentials) { data in
                    self.containedViewiOSApp17DevelopmentEssentials(dataiOSApp17DevelopmentEssentials: data)
                }
                .edgesIgnoringSafeArea([.bottom])
            }
            .navigationTitle("iOS開発の章目次")
            .navigationBarTitleDisplayMode(.inline)
        }
        //タップ後に遷移先へ遷移させる関数
        func containedViewiOSApp17DevelopmentEssentials(dataiOSApp17DevelopmentEssentials: ListiOSApp17DevelopmentEssentials) -> AnyView {
            switch dataiOSApp17DevelopmentEssentials.view {
            case .Ch1:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh1()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり13で追加
            case .Ch2:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh2()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり13で追加
            case .Ch3:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh3()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり15で追加
            case .Ch4:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh4()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり16で追加
            case .Ch5:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh5()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり17で追加
            case .Ch6:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh6()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり18で追加
            case .Ch7:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh7()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり19で追加
            case .Ch8:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh8()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり20、21で追加
            case .Ch9:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh9()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり22、23で追加
            case .Ch10:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh10()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり24で追加
            case .Ch11:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh11()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり25で追加
            case .Ch12:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh12()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり26で追加
            case .Ch13:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh13()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり27,28で追加
            case .Ch14:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh14()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり29で追加
            case .Ch15:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh15()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり31で追加
            case .Ch16:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh16()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり32で追加
            case .Ch17:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh17()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり33で追加
            case .Ch18:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh18()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり34で追加
            case .Ch19:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh19()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり35で追加
            case .Ch20:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh20()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり36で追加
            case .Ch21:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh21()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり37で追加
            case .Ch22:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh22()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり40で追加
            case .Ch23:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh23()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり41で追加
            case .Ch24:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh24()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり43で追加
            case .Ch25:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh25()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
                //じっくり44で追加
            case .Ch26:
                return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh26()) {
                    Text(dataiOSApp17DevelopmentEssentials.title)
                })
            }
        }
    }
    
    #Preview {
        iOSApp17DevelopmentEssentials()
    }

    じゃあまた次回、

    さてと新聞読んで、嫁と温泉行こ🕺
    UiPathの記事は気が向いたら書くかな藁🤣

     
     
    タダの道楽者💫ネガティブなコメント一切お断り 裾野が広がりゃそれで良い 洋書と古典とSwiftをこよなく愛するオッさん👀 色即是空🕺 記事はマガジンの方が読みやすいかな💦 ちょい難とダラダラ💃 無駄な対立軸と課題探し思考は要らんな。 変幻自在➕縦横無尽➕融通無碍🟰天衣無縫

    あなたへのおすすめ