見出し画像

【じっくりSw1ftUI37】実践編7〜第22章 SwiftUIの状態プロパティ、監視可能な状態、および環境オブジェクト①StateとBinding〜プロパティラッパーはちゃんと理解すれば怖くない

さてと、今週もよろしく🙇‍♂️前回は、

で、

ビューの配置、位置、サイズ

なんかをやったので、今回は、ビューの操作で入力した値や文字列なんかを扱う

状態プロパティ、監視可能な状態、および環境オブジェクト

をやってく〜〜〜。前回の最後でも書いたとおり、

でやった

プロパティラッパー

を実際に、

SwiftUIフレームワークで実践的に使う方法

についてなだけなところだから、

そんなに身構えなくても大丈夫🕺

Swiftの開発に限らず、すべての開発に言えることだけど、

⒈まずは機能に触れる(動かす)

⒉仕組みを理解する(ゼロから作る)

⒊何度か使ってみて慣れる(肌感覚で身につける)

さえ出来れば良いだけだからね〜〜〜!

「全部理解しないといけない」みたいに身構えると却って遠回り〜〜〜!

*ちなみに、プロパティラッパーのところでも書いたけど、

研究職で、Swiftってプログラミング言語自体を研究する人は別だけど、オイラたちの目的は、あくまでも、

自分の作りたいアプリをゼロから作れる程度に、理解すればいいだけ

だし、

みたいな本で書かれてる

プロパティラッパーを全部一回で理解しようなんて不可能

木を見て森を見ずになるだけだから、やらなくていい。
👉実際に作りながらで自分の開発に必要とかよく使うものがわかってくればその範囲で理解すればいい

だけのハナシ。

ここですでにお断り

今回の範囲をサラッと見たんだけど、結構内容盛り沢山で、ひとつの記事で全部を盛り込むと、おそらく、

  1. 内容が果てしなく長くなる

  2. 理解が難しくなる

  3. コードが山のように多くなる

👉読もうとしなくなる=記事で期待してる学習効果が得られくなる
危険性が高いな
👀💦

と判断したので、今回はStateプロパティとBindingくらいで終わらせて、

記事を数週間かけて、いくつかに分けてじっくりやってく

スタイルで記事にしてく💦なぜなら、

プロパティラッパーの理解は、
CloudKitなんかのDBを理解する前提にもなるし、そこに繋がるくらい重要

だから🌱
それに別に、数週間で記事を分けて書いていこうが、終わりさえすれば、

1年後とかで、どっちみちすべて記事になっていれば、
成果は一緒の話だからね

じっくり学習してこうって趣旨の個人の学び直しの記事なんだし、一回しっかり理解して使い熟せるようになれば、嫌でも使えるようになるんだから

焦らない。焦らない。

平日の通勤時間に毎日つぶやきで上げてる

でも、

理解に時間をかける

ってことは散々、言ってる話だし。基本に相応な時間もかけずに、

読んで理解した気になって、プロパティラッパーを使いこなせずに
動かしながらしっかり理解すれば分かるはずのバグを頻発させたり、
制作とか改修に数十倍の時間を掛けたら意味がない

からね💦

💃どうせ日本の企業は金もないし、流行りが来ないと導入も遅いから、
SwiftUIが普及するのは早くても数年後
🕺
まさに、
💃趣味こそ最強🕺

くらいの気持ちで、

💃しっかり、じっくりやってく🕺

*ちなみに、去年の今頃に、実際に転職活動をしていた際に作ってた

でも書いてる話ではあるんだけど、去年の今頃の時点で、

SwiftUIがすでにオープンソース化されて4年は経過

していたのに、

iOS開発がやっとこObjective-C👉UIKitにマイグレーションする

くらいで止まってる企業さんが多く、真顔で、

・「SwiftUIよりもUIKitができる人が欲しい」
(今更、UIKitに?
👀誰得?フレームワークって意味わかってんのか?)

・「日本は積み上げ学習だから、UIKitができるようになってからSwiftUI」
(ん?Xcodeで開発に使うフレームワークの違いだけで、UIKitができないとSwiftUIができないわけじゃないし、どうせまたUIKitからSwiftUIにマイグレーションする作業が発生するのが目に見えてるから、いきなりSwiftUIからやった方が開発コストも改修コストも少なくなるはずだが、、、ここの会社多分、オブジェクト指向言語とかフレームワークの旨みどころか、そのものが分かってる人がいないんだろうな)

と思った話は内緒にしとく!🤫
そんな実体験での例を見てもわかるとおり、

日本の企業は、組織としての導入も学びも化石レベルで遅いから、

個人の学びを焦ってやらなくていい
てか、やっちゃダメ
🙅

(iOS開発に関しては、個人で数年、好きで楽しく学んでる人の方が下手な企業よりも全然最先端のことをやってるって思っていい。
要は、企業とか研究機関とかが、「テクノロジーの民主化」を知ろうとしないから、「自分たちが最先端」って思い込んでるだけ。
オイラが一昨年〜去年の110社くらいから話を聞いてみて、実際にSwiftUIをやってますって言ってた企業は1社くらいしかなかった)

今回もいつものように、オイラの学び直しなんか関係ないって人は、

でまんま掲載はあるみたいだからそっちを参考にしてね〜〜〜
さてと、じゃ今回も早速、


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

概要では、

前回まではデータのハンドリングに関しない部分での、ビューの取り扱い方をレクチャーしてきた

みたいなことが書いてんね👀💦

インターフェース上でデータを取り扱うプロパティでSwiftUIで用意されてるオプションは主に4つで、それらはすべて

  • 状態

  • 監視

  • 環境

を制御しながら

インターフェースの表示や動作の状態を管理するもの

ってことが言いたいみたいだね👀💦で、

こんな概要だけ言われて理解できんのは触ったことがある人だけ
なんで、
早速、実際に、その4つのオプションを扱ってく〜〜〜
🕺

まずはStateから〜〜〜

いつもどおり下準備〜〜〜

import SwiftUI

struct Essentials22: View {
    var body: some View {
        ScrollView{
            Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
        }
    }
}

#Preview {
    Essentials22()
}
ハイ、完了

で、TextFieldViewを配置〜〜〜

今回はAttibuteInspectorから〜〜
てな感じで配置して〜〜〜

念の為、前回やったStackビューも入れて、プレースフォルダーの値もTOKENが出ないようにテケトーなモノにして〜〜〜

import SwiftUI

struct Essentials22: View {
    var body: some View {
        ScrollView{
            VStack{
                TextField("果物名を入力", text: fruitsName)
                Text("果物名")
            }
        }
    }
}

#Preview {
    Essentials22()
}

てな感じにすると〜〜〜

まだ存在してない引数fruitsNameを使っているからエラー発生👀💦

そこで今回の主題であるプロパティラッパーを設定。ここでは、状態を他のビューに引き継ぎたいだけだから、

import SwiftUI

struct Essentials22: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    
    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                //引き継いだプロパティラッパーを文字列として表示
                Text("\(fruitsName)")
            }
        }
    }
}

#Preview {
    Essentials22()
}
てな感じ

実際に入力してみると。。。

りんごを入力
表示された🕺
絵文字でも表示できた

さらに、ビューをわかりやすくしたいなら〜〜〜

import SwiftUI

struct Essentials22: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    
    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("\(fruitsName)")
                        .padding()
                    Text("が入力されました")

                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
            }
        }
    }
}

#Preview {
    Essentials22()
}

みたいな感じにしてあげて〜〜〜

みたいな感じ

にしてあげることもできるし、

import SwiftUI

struct Essentials22: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    
    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
            }
        }
    }
}

#Preview {
    Essentials22()
}

でここで、他のビューに引き継ぎしたいので〜〜〜

今作ったビューを他のビューにしておく

import SwiftUI

struct Essentials22: View {
    var body: some View {
        Essentials22StateView()
    }
}

#Preview {
    Essentials22()
}

struct Essentials22StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    
    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
            }
        }
    }
}

てな感じで〜〜〜

プレビュー的には変化なし👀

でさらにトグルボタンを追加して〜〜〜

struct Essentials22StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                //トグルのOnとOff時の設定を追加
                Text(fruitsBasket ? "🧺" : "")
                    .font(.largeTitle)
            }
        }
    }
}

てな感じで〜〜〜

トグルオフ〜〜〜
トグルオン〜〜〜

てな感じで、ボタンの状態変化を変数で管理することもできた〜〜〜

他の構造体で作ったビュー間でやりとりしたい:Binding

さっきの🧺を表示するかしないかを他のビューでやりたい時は〜〜〜

import SwiftUI

struct Essentials22: View {
    var body: some View {
        Essentials22StateView()
    }
}

#Preview {
    Essentials22()
}

struct Essentials22StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                Essentials22FruitsBasketView(fruitsBasket: $fruitsBasket)
            }
        }
    }
}

struct Essentials22FruitsBasketView: View {
    @State var fruitsBasket = false
    var body: some View {
        //トグルのOnとOff時の設定を追加
        Text(fruitsBasket ? "🧺" : "")
            .font(.largeTitle)
    }
}

てな感じにコードを分けると、、、

てな感じでエラーになるんだよね〜〜〜

$が不要って言ってるから、

import SwiftUI

struct Essentials22: View {
    var body: some View {
        Essentials22StateView()
    }
}

#Preview {
    Essentials22()
}

struct Essentials22StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                Essentials22FruitsBasketView(fruitsBasket: fruitsBasket)
            }
        }
    }
}

struct Essentials22FruitsBasketView: View {
    @State var fruitsBasket = false
    var body: some View {
        //トグルのOnとOff時の設定を追加
        Text(fruitsBasket ? "🧺" : "")
            .font(.largeTitle)
    }
}

てな感じで$を外しても〜〜〜

てな感じで実は🧺が表示されない

ま、これはゆーてしまえば簡単で親ビューと子ビューの間で状態の引き継ぎ(Bind)が出来てないからだけなので〜〜〜
子ビューを

struct Essentials22FruitsBasketView: View {
    @Binding var bindFruitsBasket: Bool
    var body: some View {
        //トグルのOnとOff時の設定を追加
        Text(bindFruitsBasket ? "🧺" : "")
            .font(.largeTitle)
    }
}

てな感じで

  • @State 👉 @Bindingに変更して、

  • バインディング用の変数名に変更してあげて〜〜〜

親ビューのコードを

struct Essentials22StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                Essentials22FruitsBasketView(bindFruitsBasket: $fruitsBasket)
            }
        }
    }
}

てな感じで、

  • 子ビューをBinding変数ごとしっかり定義して、

  • Binding用に設けた親ビューState変数を()内で紐つけてあげる

と、

てな感じで構造体間の状態の引き継ぎも出来た〜〜〜🕺

今回のコードまとめ

import SwiftUI

struct Essentials22: View {
    var body: some View {
        Essentials22StateView()
    }
}

#Preview {
    Essentials22()
}

struct Essentials22StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                Essentials22FruitsBasketView(bindFruitsBasket: $fruitsBasket)
            }
        }
    }
}

struct Essentials22FruitsBasketView: View {
    @Binding var bindFruitsBasket: Bool
    var body: some View {
        //トグルのOnとOff時の設定を追加
        Text(bindFruitsBasket ? "🧺" : "")
            .font(.largeTitle)
    }
}

ちなみに、変数をprivate化して、、、

import SwiftUI

struct Essentials22: View {
    var body: some View {
        Essentials22StateView()
    }
}

#Preview {
    Essentials22()
}

struct Essentials22StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State private var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State private var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                Essentials22FruitsBasketView(fruitsBasket: $fruitsBasket)
            }
        }
    }
}

struct Essentials22FruitsBasketView: View {
    @Binding var fruitsBasket: Bool
    var body: some View {
        //トグルのOnとOff時の設定を追加
        Text(fruitsBasket ? "🧺" : "")
            .font(.largeTitle)
    }
}

みたいな感じで出来なくはないんだけど、、、

オイラは個人的にこれだとどっちの変数がBindingなのかわからなくなる

ので、個人でやるときにはあんまりやらない。

今回のポイントでもあるんだけど、

Bindingを使う時は、Bindingされる親ビューにBindingで引き継ぐ用の変数なんかもないとエラーになるから気をつけてねん🕺
(2個目の@Stateを親ビューから消したら、どうなるかやってみて!!!)

と、ここまでで

すでに冒頭でゆーてたとおり、

結構長い
Observing以降は少し毛色が違う

のと、そして何より、

今からお花見に行く

から今回はここまで〜〜〜〜!

Apple公式

さてと、次回は

この章の続きで、

ObserveObjectとEnvironment

に入ってく🕺

と、後々面倒になりそうなので〜〜〜

今回のコードはここまでで、

import SwiftUI

struct Essentials22: View {
    var body: some View {
        ScrollView{
            Essentials22StateView()
        }
    }
}

#Preview {
    Essentials22()
}

struct Essentials22StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State private var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State private var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                Essentials22FruitsBasketView(fruitsBasket: $fruitsBasket)
            }
        }
    }
}

struct Essentials22FruitsBasketView: View {
    @Binding var fruitsBasket: Bool
    var body: some View {
        //トグルのOnとOff時の設定を追加
        Text(fruitsBasket ? "🧺" : "")
            .font(.largeTitle)
    }
}

こっちに改修した👀💦
プレビュー的には何も変わってないんだけど、大元のビューにScrollViewがないと多分、色々面倒になりそうと判断したので〜〜〜

さてと、いつもの記事公開後は、お花見後に余裕があれば追記しまする💦🙇‍♂️

ただいま〜〜っと!お花見満喫

さてと、記事公開後

いつもどおり

でやった感じで〜〜〜

今回は第1節〜〜〜
コンテンツ〜〜〜
コード
ポイント〜〜〜
リンク。てな感じでハイ、終了🕺

サンプルコード

◾️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
}
//各項目に表示する文字列
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),
    
]
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)
            })
        }
    }
}

#Preview {
    iOSApp17DevelopmentEssentials()
}

◾️Essentials22.swift

import SwiftUI
import WebKit

//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentialsCh22: Identifiable {
    var id: Int
    var title: String
    var view: ViewEnumiOSApp17DevelopmentEssentialsCh22
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentialsCh22{
    case Sec1
}
//各項目に表示するリスト項目
let dataiOSApp17DevelopmentEssentialsCh22: [ListiOSApp17DevelopmentEssentialsCh22] = [
    ListiOSApp17DevelopmentEssentialsCh22(id: 1, title: essentialsChapter22_1SubTitle, view: .Sec1)
]
struct iOSApp17DevelopmentEssentialsCh22: View {
    var body: some View {
        VStack {
            Divider()
            List (dataiOSApp17DevelopmentEssentialsCh22) { data in
                self.containedViewiOSApp17DevelopmentEssentialsCh22(dataiOSApp17DevelopmentEssentialsCh22: data)
            }
            .edgesIgnoringSafeArea([.bottom])
        }
        .navigationTitle(essentialsChapter22NavigationTitle)
        .navigationBarTitleDisplayMode(.inline)
    }
    //タップ後に遷移先へ遷移させる関数
    func containedViewiOSApp17DevelopmentEssentialsCh22(dataiOSApp17DevelopmentEssentialsCh22: ListiOSApp17DevelopmentEssentialsCh22) -> AnyView {
        switch dataiOSApp17DevelopmentEssentialsCh22.view {
        case .Sec1:
            return AnyView(NavigationLink (destination: Essentials22_1()) {
                Text(dataiOSApp17DevelopmentEssentialsCh22.title)
            })
        }
    }
}
#Preview {
    iOSApp17DevelopmentEssentialsCh22()
}
//Essentials22.swift
struct Essentials22_1: View {
    var body: some View {
        VStack{
            TabView {
                Essentials22_1Contents()
                    .tabItem {
                        Image(systemName: contentsImageTab)
                        Text(contentsTextTab)
                    }
                Essentials22_1Code()
                    .tabItem {
                        Image(systemName: codeImageTab)
                        Text(codeTextTab)
                    }
                Essentials22_1Points()
                    .tabItem {
                        Image(systemName: pointImageTab)
                        Text(pointTextTab)
                    }
                Essentials22_1WEB()
                    .tabItem {
                        Image(systemName: webImageTab)
                        Text(webTextTab)
                    }
            }
        }
    }
}
#Preview {
    Essentials22_1()
}

struct Essentials22_1Contents: View {
    var body: some View {
        ScrollView{
            Essentials22_1StateView()
        }
    }
}

#Preview {
    Essentials22_1Contents()
}

struct Essentials22_1StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State private var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State private var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                Essentials22_1FruitsBasketView(fruitsBasket: $fruitsBasket)
            }
        }
    }
}

struct Essentials22_1FruitsBasketView: View {
    @Binding var fruitsBasket: Bool
    var body: some View {
        //トグルのOnとOff時の設定を追加
        Text(fruitsBasket ? "🧺" : "")
            .font(.largeTitle)
    }
}

struct Essentials22_1Code: View {
    var body: some View {
        ScrollView{
            Text(codeEssentials22_1)
        }
    }
}
#Preview {
    Essentials22_1Code()
}
struct Essentials22_1Points: View {
    var body: some View {
        ScrollView{
            Text(pointEssentials22_1)
        }
    }
}
#Preview {
    Essentials22_1Points()
}

struct Essentials22_1WebView: 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 Essentials22_1WEB: View {
    private var url:URL = URL(string: urlEssentials22_1)!
    var body: some View {Essentials22_1WebView(searchURL: url)
    }
}
#Preview {
    Essentials22_1WEB()
}

//コード
let codeEssentials22_1 = """
struct Essentials22_1Contents: View {
    var body: some View {
        ScrollView{
            Essentials22_1StateView()
        }
    }
}

#Preview {
    Essentials22_1Contents()
}

struct Essentials22_1StateView: View {
    //ここにプロパティラッパーと初期値を設定
    @State private var fruitsName = ""
    //トグルボタン用のプロパティラッパーを追加して、初期値を設定
    @State private var fruitsBasket = false

    var body: some View {
        ScrollView{
            VStack{
                //プロパティラッパーを$付きで設定
                TextField("果物名を入力", text: $fruitsName)
                    .background(Color.yellow)
                //引き継いだプロパティラッパーを文字列として表示
                HStack{
                    Text("入力されたのは、")
                        .padding()
                    Text("\\(fruitsName)")
                        .padding()
                    Text("です")
                        .padding()
                }
                .aspectRatio(contentMode: .fill)
                .border(Color.black)
                //トグルボタンを追加
                Toggle(isOn: $fruitsBasket) {
                    Text("かごを付けますか?")
                }
                Essentials22_1FruitsBasketView(fruitsBasket: $fruitsBasket)
            }
        }
    }
}

struct Essentials22_1FruitsBasketView: View {
    @Binding var fruitsBasket: Bool
    var body: some View {
        //トグルのOnとOff時の設定を追加
        Text(fruitsBasket ? "🧺" : "")
            .font(.largeTitle)
    }
}
"""

//タイトル
let essentialsChapter22NavigationTitle = "第22章"
let essentialsChapter22Title = "第22章 SwiftUIの状態プロパティ、監視可能な状態、および環境オブジェクト"
let essentialsChapter22_1SubTitle = "第1節 StateとBinding"

//ポイント
let pointEssentials22_1 = """
インターフェース上でデータを取り扱うプロパティでSwiftUIで用意されてるオプションは主に4つで、それらはすべて
・状態
・監視
・環境
を制御しながら
インターフェースの表示や動作の状態を管理するもの

Bindingを使う時は、Bindingされる親ビューにBindingで引き継ぐ用の変数なんかもないとエラーになるから気をつけて
"""
//URL
let urlEssentials22_1 = "https://note.com/m_kakudo/n/ne0c79a7ef90d"

以上。

いやあ、桜とか菜の花とか綺麗だったし、嫁と飲む酒は最高でござる
暦通りの休日にこれをしたいから、
独学で資格取ったり、経験積んだりして、エンジニアになったしね👀

この記事が気に入ったらサポートをしてみませんか?