見出し画像

【じっくりSw1ftUI35】実践編5〜第20章 SwiftUIを使用したカスタムビューの作成〜TextViewで遊ぼう

さてと、前回

までで

Xcodeの機能と階層構造、全体像の復習

までは終わったので〜〜〜。

今日から個別に実践編

👉Xcodeで直にコードを書いてく〜〜〜🕺

以下は、オイラの学び直しになるので、そんなもん要らんって人は、

で、今回のコードも載ってるみたいだからテケトーにどうぞ〜〜〜

どうでもいいことなのかもしれないけど

最初のうちは

なるべくちゃんとコードを打ちながら
動きがどうなるか

をちゃんと見ながらやってみてね〜〜〜

モディファイアを打ち込む位置とかだけで微妙に変化してく
👉その感覚を身につけとかないと結構、UIデザインで苦労するからね👀💦

さてと、


じっくり第20章を読んでく🕺

冒頭では、

ビューの表示とか振る舞いをいい感じにカスタマイズしてこうぜ!
な方法をこの章から説明してく〜〜〜

みたいなことを書いてんね

まずは、今回用の新規ビューを追加

新規のSwiftUIファイル>Essentials20って名前で追加〜〜〜

てな感じで立て付けでコードが自動追加されてるね👀

オイラの場合は、プロジェクトファイルを新規で追加して、コンテントビューに書き込まれたわけじゃないので、

import SwiftUI

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

#Preview {
    Essentials20()
}

こんな感じのコードが出来上がってる🕺

今回は節が10個以上ありそうなんで、ここで、

Scrolleviewを追加しとく

追加すると、中央にあったテキストビューの表示が、
上部に移動したね👀💦

変更後のコードはこんな感じ💦

import SwiftUI

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

#Preview {
    Essentials20()
}

準備ができたので〜〜ここから

本編を読み進めてく

他のタグなんかの例に漏れず、SwiftUIも

bodyにコンテンツの内容を追加して、Viewプロトコルを調整してく
👉テキストラベル、ボタン、テキストフィールド、メニュー、トグル、レイアウトマネージャビューみたいなビューを使って自分の操作画面(UI)をカスタマイズしてく

みたいなことが書いてんね。
他にもサイズとか複雑なことをしたいって時も。。。
みたいなことも書いてんね。要は

ビューの表示とか振る舞い自体は、自分でいくらでもカスタマイズでいい感じにできるよ!!!
しかも、コンポーネントでログなんかも吐き出してるからね

てことが言いたいみたい。
と、概要を掴んだところで、「論より証拠、案ずるより生むが易し」

早速、プロットに沿ってコードを動かしてみよう!

まずは、同じコードの構成にコードを手直し

import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
            }
            .padding()
        }
    }
}

#Preview {
    Essentials20()
}

ここまで見てるだけでも、

  • ContentViewか否かの違いだけで、ビューはどこでも組み込んだコードのとおりに表示される=自分でいい感じにできる

  • VStack{}やImage()、Text()ってのがビューで、その後ろに、メソッドチェーンで.で色々なモディファイアでビュー自体の調整がされてる

てことが分かると思う👀💦

テキストビューをもう1個追加してみよう

import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
            }
            .padding()
        }
    }
}

#Preview {
    Essentials20()
}

さらに〜〜〜

import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
            }
            .padding()
        }
    }
}

#Preview {
    Essentials20()
}

お次は階層構造を説明してんね

冒頭のリンク先の図を流用して〜〜〜

てな感じだよん
てな感じでプレビューもしっかり表示ができて〜〜〜
import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    Text("🍎ちゃん")
                    Text("🍊くん")
                }
            }
            Text("🍇先輩、ちゃーっす")
            .padding()
        }
    }
}

#Preview {
    Essentials20()
}

コードも追加したビュー分だけしか増加してないのが分かるよね〜〜〜👀🕺

なんてシンプルで簡単なんだ🤤

テキストビューを繋いでみよう

おお!テキストにさらにテキストが追加できた🕺
import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    //5節で追加
                    Text("🍎ちゃん") + Text("大好き😍")
                    Text("🍊くん") + Text("好き❤️")
                }
            }
            Text("🍇先輩、ちゃーっす")
            .padding()
        }
    }
}

#Preview {
    Essentials20()
}

他のビューから呼び出し〜〜〜

import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    //5節で追加
                    Text("🍎ちゃん") + Text("大好き😍")
                    Text("🍊くん") + Text("好き❤️")
                }
                //6節で追加
                Esstials20FruitsView()
            }
            Text("🍇先輩、ちゃーっす")
            .padding()
        }
    }
}

#Preview {
    Essentials20()
}
//6節で追加
struct Esstials20FruitsView : View {
    var body: some View {
        VStack{
            Text("🍇先輩に呼び出されてきました")
            Text("🍈")
            Text("🍌")
        }
    }
}

てな感じのコードを追加して〜〜〜

🍈と🍌が横並びの方がいいなら〜〜〜
//6節で追加
struct Esstials20FruitsView : View {
    var body: some View {
        //HStackに変えるだけ
        HStack{
            Text("🍇先輩に呼び出されてきました")
            Text("🍈")
            Text("🍌")
        }
    }
}
こんな感じで簡単に調整も出来た
👉コードベースなのでコードを変えるだけ🕺

プロパティを設定してみよう

//7節で追加
struct Esstials20CarView : View {
    var body: some View {
        HStack{
            Image(systemName: "car.fill")
            Text("車屋さん")
                .font(.largeTitle)
            Image(systemName: "car.fill")
        }
    }
}

てな感じのビューをもう1個作って〜〜〜

import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    //5節で追加
                    Text("🍎ちゃん") + Text("大好き😍")
                    Text("🍊くん") + Text("好き❤️")
                }
                //6節で追加
                Esstials20FruitsView()
                //7節で追加
                Esstials20CarView()
            }
            Text("🍇先輩、ちゃーっす")
            .padding()
        }
    }
}

#Preview {
    Essentials20()
}
//6節で追加
struct Esstials20FruitsView : View {
    var body: some View {
        //HStackに変えるだけ
        HStack{
            Text("🍇先輩に呼び出されてきました")
            Text("🍈")
            Text("🍌")
        }
    }
}
//7節で追加
struct Esstials20CarView : View {
    var body: some View {
        HStack{
            Image(systemName: "car.fill")
            Text("車屋さん")
                .font(.largeTitle)
            Image(systemName: "car.fill")
        }
    }
}

てな感じで〜〜〜

車屋さんのビューも追加できた〜〜〜

お次はモディファイアでちょっといい感じに〜〜〜

struct Esstials20CarView : View {
    var body: some View {
        HStack{
            Image(systemName: "car.fill")
                //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
            Text("車屋さん")
                .font(.largeTitle)
                //8節で追加
                .foregroundStyle(.green)
            Image(systemName: "car.fill")
                //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
        //8節で追加
        .lineSpacing(2)
        .padding()
    }
}

てな感じで

てな感じで調整もできたし〜〜〜

テキストスタイルは自分でカスタマイズできる

冒頭のリンクの文章を引用するけど

”iOS では、アプリケーションがテキストを表示するときに採用することが期待される優先テキスト サイズをユーザーが選択できます。現在のテキスト サイズは、デバイスの[設定] -> [表示と明るさ] -> [テキスト サイズ]画面で構成できます。”

らしい👀💦(知らんかった、、さすがEssentials)

と、コードだけ〜〜〜

//9節で追加
struct Esstials20TextModifierView : View {
    var body: some View {
        VStack{
            Text("新鮮な果物屋")
                .font(.custom("Copperplate", size: 50))
                .fontWeight(.bold)
        }
        .padding()
    }
}
おお!👀すげ〜〜〜

お次はモディファイアの順番(ここが今回一番大事かも👀💦)

struct Esstials20TextModifierView : View {
    var body: some View {
        VStack{
            Text("新鮮な果物屋")
                .font(.custom("Copperplate", size: 50))
                .fontWeight(.bold)
                //10節で追加
                .border(Color.purple)
                .padding()
        }
        .padding()
    }
}

でやると〜〜〜

てな感じで、自分このままやったらキッチキチやぞ(オール巨人師匠)

みたいになってるから〜〜〜

struct Esstials20TextModifierView : View {
    var body: some View {
        VStack{
            Text("新鮮な果物屋")
                .font(.custom("Copperplate", size: 50))
                .fontWeight(.bold)
                //10節で追加(順番を入れ替え後〜〜)
                .padding()
                .border(Color.purple)
        }
        .padding()
    }
}
てな感じで余白が大きくなった〜〜〜
これなら師匠にも怒られまい💦

でさらに〜〜〜サイズを変更しても、

struct Esstials20TextModifierView : View {
    var body: some View {
        VStack{
            Text("新鮮な果物屋")
                .font(.custom("Copperplate", size: 100))
                .fontWeight(.bold)
                //10節で追加(順番を入れ替え後〜〜)
                .padding()
                .border(Color.purple)
        }
        .padding()
    }
}
被らなくなったね👀🕺

さらにメソッドチェーンでモディファイアをどんどん追加してカスタマイズしちゃおう

//11節で追加
struct Esstials20CustomModifierView : View {
    var body: some View {
        VStack{
            Text("うちの果物大安売り")
                .foregroundColor(.pink)
                .font(.largeTitle)
                .background(Color.white)
                .border(Color.red,width: 2.5)
                .shadow(color: .orange, radius:10,x:0,y: 7)
        }
        .padding()
    }
}

てなビューを追加して、

import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    //5節で追加
                    Text("🍎ちゃん") + Text("大好き😍")
                    Text("🍊くん") + Text("好き❤️")
                }
                //6節で追加
                Esstials20FruitsView()
                //7節で追加
                Esstials20CarView()
                //9節で追加
                Esstials20TextModifierView()
                //11節で追加
                Esstials20CustomModifierView()
            }
            Text("🍇先輩、ちゃーっす")
                .padding()
        }
    }
}

#Preview {
    Essentials20()
}
//6節で追加
struct Esstials20FruitsView : View {
    var body: some View {
        //HStackに変えるだけ
        HStack{
            Text("🍇先輩に呼び出されてきました")
            Text("🍈")
            Text("🍌")
        }
    }
}
//7節で追加
struct Esstials20CarView : View {
    var body: some View {
        HStack{
            Image(systemName: "car.fill")
            //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
            Text("車屋さん")
                .font(.largeTitle)
            //8節で追加
                .foregroundStyle(.green)
            Image(systemName: "car.fill")
            //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
        //8節で追加
        .lineSpacing(2)
        .padding()
    }
}
//9節で追加
struct Esstials20TextModifierView : View {
    var body: some View {
        VStack{
            Text("新鮮な果物屋")
                .font(.custom("Copperplate", size: 100))
                .fontWeight(.bold)
                //10節で追加(順番を入れ替え後〜〜)
                .padding()
                .border(Color.purple)
        }
        .padding()
    }
}
//11節で追加
struct Esstials20CustomModifierView : View {
    var body: some View {
        VStack{
            Text("うちの果物大安売り")
                .foregroundColor(.pink)
                .font(.largeTitle)
                .background(Color.white)
                .border(Color.red,width: 2.5)
                .shadow(color: .orange, radius:10,x:0,y: 7)
        }
        .padding()
    }
}

てな感じにすると〜〜〜

てな感じで良い感じがさらに良い感じに〜〜〜

さらにモディファイア自体を定義するViewModifier構造体を追加〜〜〜

//11節で追加
struct Essentials20ViewModifier : ViewModifier{
    func body(content: Content) -> some View {
        content
            .padding()
            .foregroundColor(.pink)
            .font(.largeTitle)
            .background(Color.white)
            .border(Color.red,width: 2.5)
            .shadow(color: .orange, radius:10,x:0,y: 7)
    }
}

みたいな感じで作って〜、コイツを

//11節で追加
struct Esstials20CustomModifierView : View {
    var body: some View {
        VStack{
            Text("うちの果物大安売り")
                .foregroundColor(.pink)
                .font(.largeTitle)
                .background(Color.white)
                .border(Color.red,width: 2.5)
                .shadow(color: .orange, radius:10,x:0,y: 7)
        }
        .padding()
    }
}

のコードのモディファイアにぶっ込んじゃうと、

//11節で追加
struct Esstials20CustomModifierView : View {
    var body: some View {
        VStack{
            Text("うちの果物大安売り")
                .modifier(Essentials20ViewModifier())
        }
        .padding()
    }
}
おお👀いい感じ

ここでポイント①

ViewModifierでやるかどうかは自由なんだけど、

  • 微妙な調整が各ビューで必要な場合:普通のモディファイア

  • 各ビューを統一的に整えたい:ViewModifier

って感じで使い分けた方がいいかな〜〜〜って感じだね🧐

クリックイベントを組み込んでみよう

ボタンを押すとテキストの数値が増減する

//12節で追加
struct Essentials20ClickableEventView: View {
    @State var menuNum = 0
    var body: some View {
        HStack{
            Button(action:clickableButtonMinus1){
                Text("-")
                    .foregroundColor(.white)
                    .frame(width: 44,height: 44)
            }
            .background(Color.green)
            .border(Color.red)
            .padding()
            
            Text("\(menuNum)")
                .foregroundColor(.blue)
                .frame(width: 44,height: 44)
                .padding()
            Button(action:clickableButtonPlus1){
                Text("+")
                    .foregroundColor(.white)
                    .frame(width: 44,height: 44)
            }
            .background(Color.green)
            .border(Color.red)
            .padding()
        }
    }
    func clickableButtonMinus1(){
        menuNum -= 1
    }
    func clickableButtonPlus1(){
        menuNum += 1
    }
}

てな感じのコードで〜〜〜

+ボタンを1回タップすると〜〜〜
数字がひとつ増えた〜〜〜
マイナスボタンを2回タップ〜〜
てな感じで👀

関数を使わないボタンビューの例

//12節で追加
struct Essentials20PrintEventView: View {
    var body: some View {
        Button(action: {
            print("タップされました!")
        }){
            Image(systemName: "tray.2.fill")
        }
    }
}

てな感じで

🫣完了

コンテナビューあれこれ〜〜〜

//13節で追加
struct Essentials20ContainerView: View {
    var body: some View {
        Text("乗り物集まれ")
            .font(.largeTitle)
            .fontWeight(.bold)
            .foregroundColor(.orange)
            .padding()
            .border(.orange)
        VStack(spacing:15){
            HStack(spacing:5){
                Text("🚗")
                    .modifier(Essentials20FontModifier())
                Text("🛵")
                    .modifier(Essentials20FontModifier())
                Text("🚲")
                    .modifier(Essentials20FontModifier())
            }
            HStack(spacing:5){
                Text("🚢")
                    .modifier(Essentials20FontModifier())
                Text("🚞")
                    .modifier(Essentials20FontModifier())
                Text("✈️")
                    .modifier(Essentials20FontModifier())
            }
        }
    }
}
struct Essentials20FontModifier: ViewModifier{
    func body(content: Content) -> some View {
        content
            .font(.system(size: 44))
            .padding()
    }
}

てな感じのコードで〜〜〜

調整できた〜〜〜🕺

他にも、クロージャを使って〜〜〜

本編に載ってるコードとしては、

//13節で追加
struct Essentials20ContainerClosureView<Content :View>: View {
    let content:() -> Content
    init(@ViewBuilder content: @escaping()-> Content) {
        self.content = content
    }
    var body: some View {
        VStack(spacing: 15){
            content()
                .padding()
                .border(.brown,width: 3)
        }
        .font(.largeTitle)
    }
}

てな感じのコードなんだけど、律儀にコードを組み込もうとすると、

みたいな感じでエラーになる👀💦

何でだろうと、調べてみたんだけど

なんかを参考に、まあ、一回仕組みを理解しようとすると当たり前なんだけど、

ViewBuilderはそれ自体がビューではないから、
それ自体がViewではないって当たり前の話💦

つまり、

struct Essentials20ViewBuiltView:View {
    var body: some View {
        Essentials20ContainerClosureView{
            VStack{
                Text("おいしい果物をいろんな乗り物に乗せて全国にお届け🕺")
            }
        }
    }
}

みたいなViewを作って、組み込んであげると

てな感じで

できるって話〜〜〜🕺

ここでポイント②

ViewModifierやViewBuilderは、組み込むViewを用意する
👉単体ではビューではない
(当たり前の話だけどね👀💦)

Labelビューを使ってみよう

てな感じでLabelビュー用のビューを追加して〜〜〜
おお!なんかSFシンボルズを呼び出せるんだね〜〜〜
ま、数が多いからオイラは
で直接探すのが好きだけど、、、👀

でテケトーにドラッグして追加をすると

こんな感じ👀❣️

でここにLabelビューを追加してカスタマイズしてくと

てな感じで追加できた🕺

コードはこんな感じ

struct Essentials20LabelView: View {
    var body: some View {
        HStack{
            Label("pencil", systemImage: "pencil.circle.fill")
        }
    }
}

さらに〜〜〜

struct Essentials20LabelView: View {
    var body: some View {
        HStack{
            Label("🍎果物屋さん🍊",systemImage: "")
        }
        .font(.largeTitle)
    }
}

てな感じにして

こんな感じのラベルにもできる

さらにさらに〜〜

struct Essentials20LabelView: View {
    var body: some View {
        HStack{
            Text("copyright")
                .font(.caption)
            Label("Apple.Inc",systemImage: "apple.logo")
        }
        .font(.largeTitle)
        .foregroundColor(.gray)
    }
}

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

みんな大好きAppleのロゴマークを使ってこんな感じにもできちゃう🕺

ここまでで言わなくても分かると思うけど

で紹介した、イメージとかを格納する

Assets

は一切使用しなくても、画像は結構、立て付けの機能を活用すれば簡単にリッチな表現ができるって分かると思う!しかも、AutoLayoutって概念がSwiftUIにないから、横に向けても、

てな感じで自動で補正してくれてる👀
(*より詳細な位置を設定したいときはサイズクラスってヤツを使えば出来たりする
けど、ここでは割愛)

で、最後にラベルのレンダリング〜〜〜

struct Essentials20LabelView: View {
    var body: some View {
        VStack{
            HStack{
                Text("copyright")
                    .font(.caption)
                Label("Apple.Inc",systemImage: "apple.logo")
            }
            .font(.largeTitle)
            .foregroundColor(.gray)
            //レンダリング
            HStack{
                Text("copyright")
                    .font(.caption)
                Label(
                    title: { Text("Apple.Inc") },
                    icon: { Image(systemName: "apple.logo") })
                
            }
            .font(.largeTitle)
            .foregroundColor(.black)
        }
    }
}
黒文字で対比させてみた

みたいなこともできなくはないみたいだけど、ここは本当にどっちでやりたいかで、

  • その時々の必要性

  • 開発してる組織のコーディング規約

によるかなあ🧐って感じ
本編の内容としては以上🕺

今回のコードまとめ

import SwiftUI

struct Essentials20: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    //5節で追加
                    Text("🍎ちゃん") + Text("大好き😍")
                    Text("🍊くん") + Text("好き❤️")
                }
                //6節で追加
                Esstials20FruitsView()
                //7節で追加
                Esstials20CarView()
                //9節で追加
                Esstials20TextModifierView()
                //11節で追加
                Esstials20CustomModifierView()
                //12節で追加
                Essentials20ClickableEventView()
                //12節で追加
                Essentials20PrintEventView()
                //13節で追加
                Essentials20ContainerView()
                Essentials20ViewBuiltView()
                //14節で追加
                Essentials20LabelView()
            }
            Text("🍇先輩、ちゃーっす")
                .padding()
        }
    }
}

#Preview {
    Essentials20()
}
//6節で追加
struct Esstials20FruitsView : View {
    var body: some View {
        //HStackに変えるだけ
        HStack{
            Text("🍇先輩に呼び出されてきました")
            Text("🍈")
            Text("🍌")
        }
    }
}
//7節で追加
struct Esstials20CarView : View {
    var body: some View {
        HStack{
            Image(systemName: "car.fill")
            //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
            Text("車屋さん")
                .font(.largeTitle)
            //8節で追加
                .foregroundStyle(.green)
            Image(systemName: "car.fill")
            //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
        //8節で追加
        .lineSpacing(2)
        .padding()
    }
}
//9節で追加
struct Esstials20TextModifierView : View {
    var body: some View {
        VStack{
            Text("新鮮な果物屋")
                .font(.custom("Copperplate", size: 100))
                .fontWeight(.bold)
            //10節で追加(順番を入れ替え後〜〜)
                .padding()
                .border(Color.purple)
        }
        .padding()
    }
}
//11節で追加
struct Esstials20CustomModifierView : View {
    var body: some View {
        VStack{
            Text("うちの果物大安売り")
                .modifier(Essentials20ViewModifier())
        }
        .padding()
    }
}
//11節で追加
struct Essentials20ViewModifier : ViewModifier{
    func body(content: Content) -> some View {
        content
            .padding()
            .foregroundColor(.pink)
            .font(.largeTitle)
            .background(Color.white)
            .border(Color.red,width: 2.5)
            .shadow(color: .orange, radius:10,x:0,y: 7)
    }
}
//12節で追加
struct Essentials20ClickableEventView: View {
    @State var menuNum = 0
    var body: some View {
        HStack{
            Button(action:clickableButtonMinus1){
                Text("-")
                    .foregroundColor(.white)
                    .frame(width: 44,height: 44)
            }
            .background(Color.green)
            .border(Color.red)
            .padding()
            
            Text("\(menuNum)")
                .foregroundColor(.blue)
                .frame(width: 44,height: 44)
                .padding()
            Button(action:clickableButtonPlus1){
                Text("+")
                    .foregroundColor(.white)
                    .frame(width: 44,height: 44)
            }
            .background(Color.green)
            .border(Color.red)
            .padding()
        }
    }
    func clickableButtonMinus1(){
        menuNum -= 1
    }
    func clickableButtonPlus1(){
        menuNum += 1
    }
}
//12節で追加
struct Essentials20PrintEventView: View {
    var body: some View {
        Button(action: {
            print("タップされました!")
        }){
            Image(systemName: "tray.2.fill")
        }
    }
}
//13節で追加
struct Essentials20ContainerView: View {
    var body: some View {
        Text("乗り物集まれ")
            .font(.largeTitle)
            .fontWeight(.bold)
            .foregroundColor(.orange)
            .padding()
            .border(.orange)
        VStack(spacing:15){
            HStack(spacing:5){
                Text("🚗")
                    .modifier(Essentials20FontModifier())
                Text("🛵")
                    .modifier(Essentials20FontModifier())
                Text("🚲")
                    .modifier(Essentials20FontModifier())
            }
            HStack(spacing:5){
                Text("🚢")
                    .modifier(Essentials20FontModifier())
                Text("🚞")
                    .modifier(Essentials20FontModifier())
                Text("✈️")
                    .modifier(Essentials20FontModifier())
            }
        }
    }
}
struct Essentials20FontModifier: ViewModifier{
    func body(content: Content) -> some View {
        content
            .font(.system(size: 44))
            .padding()
    }
}
//13節で追加
struct Essentials20ContainerClosureView<Content :View>: View {
    let content:() -> Content
    init(@ViewBuilder content: @escaping()-> Content) {
        self.content = content
    }
    var body: some View {
        VStack(spacing: 15){
            content()
                .padding()
                .border(.brown,width: 3)
        }
        .font(.largeTitle)
    }
}

struct Essentials20ViewBuiltView:View {
    var body: some View {
        Essentials20ContainerClosureView{
            VStack{
                Text("おいしい果物をいろんな乗り物に乗せて全国にお届け🕺")
            }
        }
    }
}

struct Essentials20LabelView: View {
    var body: some View {
        VStack{
            HStack{
                Text("copyright")
                    .font(.caption)
                Label("Apple.Inc",systemImage: "apple.logo")
            }
            .font(.largeTitle)
            .foregroundColor(.gray)
            //レンダリング
            HStack{
                Text("copyright")
                    .font(.caption)
                Label(
                    title: { Text("Apple.Inc") },
                    icon: { Image(systemName: "apple.logo") })
                
            }
            .font(.largeTitle)
            .foregroundColor(.black)
        }
    }
}

ここからは毎度恒例のよもやま話

①なんでそんなにいちいちビューを分けてんの👀?

今までのコードを見ていて、

ひとつのビューに書き込めばいいのに、、、

って思った人は多いと思うし、それが普通なんだけど、実はSwiftUIには、

Groupを使わない限り、
ひとつのStackViewにおけるビューは10個まで

って、本編にも載っていない制約があったのと、ビューの中に組み込む個別のビューを呼び出す方が、部分的な改修が簡単なことが多いので〜〜〜!

ま、昔の習わしで今は解消してるかもしれないけど、
Stackビューの中にギッチギチに詰め込んでも、

それが果たしてユーザーさんが使いやすいUIになっているのか🧐

って最大の問題が控えてるからね。

②スクロールビューを外すとどうなるの?👀

struct Essentials20: View {
    var body: some View {
//        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    //5節で追加
                    Text("🍎ちゃん") + Text("大好き😍")
                    Text("🍊くん") + Text("好き❤️")
                }
                //6節で追加
                Esstials20FruitsView()
                //7節で追加
                Esstials20CarView()
                //9節で追加
                Esstials20TextModifierView()
                //11節で追加
                Esstials20CustomModifierView()
                //12節で追加
                Essentials20ClickableEventView()
                //12節で追加
                Essentials20PrintEventView()
                //13節で追加
                Essentials20ContainerView()
                Essentials20ViewBuiltView()
                //14節で追加
                Essentials20LabelView()
            }
            Text("🍇先輩、ちゃーっす")
                .padding()
//        }
    }
}

てな感じで実際にコメントアウトすると〜〜〜

てな感じで全部表示しきれなくなる上に、上下にもスクロールできないのでこのまんま👀

ま、普通のアプリだったら

ひとつのビューにこんなに情報を盛り込まない

って思ってるかもしれないけど、

市役所みたいな日本の会社のポータルサイト見てみ?👀
あれで情報を少なくしてシンプルに作ってるつもりだからね

依頼してくるクライアントさんなんて、その常識がないし、全体の設計なんか考えてなくて目先で

アプリの常識なんか知らない
このページに飛ぶボタンを追加してほしい

って感じで場当たり的に要求してくるんだから、、、💦

今回やったところだけでも

さらっと斜め読みしてわかった気にならずに、
結構、色々なUIが作れると思うから
ま、実際に動かしてやってみてね〜〜〜🕺
じゃないと毎週、1記事ずつなんてペースでわざわざ記事になんてしないし〜〜〜
Stackとかframeはまだ未出だけど、以降の章でじっくりやってくから🕺

UIKitでのラベルとボタンの作り方を比較したい人は

なんかの記事も載せてるから参考にしてみてね。実際に動かして対比すると、

いかにSwiftUIフレームワークがよりシンプルでモダンに進化してるか

が分かると思う👀

Apple公式

さてと、次回は

この章でも出てきた

StackとFrame

について扱った

第21章:SwiftUI のスタックとフレーム

についてやってく〜〜〜🕺

記事公開後、

のやり方で今回も組み込み〜〜〜

てな
てな
感じの
てな感じで
ハイ!完了🕺

コード

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

#Preview {
    iOSApp17DevelopmentEssentials()
}

◾️Essentials20.swift

import SwiftUI
import WebKit

//iOSApp17DevelopmentEssentialsCh20.swift
//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentialsCh20: Identifiable {
    var id: Int
    var title: String
    var view: ViewEnumiOSApp17DevelopmentEssentialsCh20
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentialsCh20 {
    case Sec1
}
//各項目に表示するリスト項目
let dataiOSApp17DevelopmentEssentialsCh20: [ListiOSApp17DevelopmentEssentialsCh20] = [
    ListiOSApp17DevelopmentEssentialsCh20(id: 1, title: essentialsChapter20SubTitle, view: .Sec1)
]
struct iOSApp17DevelopmentEssentialsCh20: View {
    var body: some View {
        VStack {
            Divider()
            List (dataiOSApp17DevelopmentEssentialsCh20) { data in
                self.containedViewiOSApp17DevelopmentEssentialsCh20(dataiOSApp17DevelopmentEssentialsCh20: data)
            }
            .edgesIgnoringSafeArea([.bottom])
        }
        .navigationTitle(essentialsChapter20NavigationTitle)
        .navigationBarTitleDisplayMode(.inline)
    }
    //タップ後に遷移先へ遷移させる関数
    func containedViewiOSApp17DevelopmentEssentialsCh20(dataiOSApp17DevelopmentEssentialsCh20: ListiOSApp17DevelopmentEssentialsCh20) -> AnyView {
        switch dataiOSApp17DevelopmentEssentialsCh20.view {
        case .Sec1:
            return AnyView(NavigationLink (destination: Essentials20()) {
                Text(dataiOSApp17DevelopmentEssentialsCh20.title)
            })
        }
    }
}
#Preview {
    iOSApp17DevelopmentEssentialsCh20()
}
//Essentials4.swift
struct Essentials20: View {
    var body: some View {
        VStack{
            TabView {
                Essentials20Contents()
                    .tabItem {
                        Image(systemName: contentsImageTab)
                        Text(contentsTextTab)
                    }
                Essentials20Code()
                    .tabItem {
                        Image(systemName: codeImageTab)
                        Text(codeTextTab)
                    }
                Essentials20Points()
                    .tabItem {
                        Image(systemName: pointImageTab)
                        Text(pointTextTab)
                    }
                Essentials20WEB()
                    .tabItem {
                        Image(systemName: webImageTab)
                        Text(webTextTab)
                    }
            }
        }
    }
}
#Preview {
    Essentials20()
}

struct Essentials20Code: View {
    var body: some View {
        ScrollView{
            Text(codeEssentials20)
        }
    }
}
#Preview {
    Essentials20Code()
}
struct Essentials20Points: View {
    var body: some View {
        ScrollView{
            Text(pointEssentials20)
        }
    }
}
#Preview {
    Essentials20Points()
}
struct Essentials20WebView: 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 Essentials20WEB: View {
    private var url:URL = URL(string: urlEssentials20)!
    var body: some View {Essentials4WebView(searchURL: url)
    }
}
#Preview {
    Essentials20WEB()
}
//コード
let codeEssentials20 = """
struct Essentials20Contents: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    //5節で追加
                    Text("🍎ちゃん") + Text("大好き😍")
                    Text("🍊くん") + Text("好き❤️")
                }
                //6節で追加
                Esstials20FruitsView()
                //7節で追加
                Esstials20CarView()
                //9節で追加
                Esstials20TextModifierView()
                //11節で追加
                Esstials20CustomModifierView()
                //12節で追加
                Essentials20ClickableEventView()
                //12節で追加
                Essentials20PrintEventView()
                //13節で追加
                Essentials20ContainerView()
                Essentials20ViewBuiltView()
                //14節で追加
                Essentials20LabelView()
            }
            Text("🍇先輩、ちゃーっす")
                .padding()
        }
    }
}

#Preview {
    Essentials20Contents()
}
//6節で追加
struct Esstials20FruitsView : View {
    var body: some View {
        //HStackに変えるだけ
        HStack{
            Text("🍇先輩に呼び出されてきました")
            Text("🍈")
            Text("🍌")
        }
    }
}
//7節で追加
struct Esstials20CarView : View {
    var body: some View {
        HStack{
            Image(systemName: "car.fill")
            //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
            Text("車屋さん")
                .font(.largeTitle)
            //8節で追加
                .foregroundStyle(.green)
            Image(systemName: "car.fill")
            //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
        //8節で追加
        .lineSpacing(2)
        .padding()
    }
}
//9節で追加
struct Esstials20TextModifierView : View {
    var body: some View {
        VStack{
            Text("新鮮な果物屋")
                .font(.custom("Copperplate", size: 100))
                .fontWeight(.bold)
            //10節で追加(順番を入れ替え後〜〜)
                .padding()
                .border(Color.purple)
        }
        .padding()
    }
}
//11節で追加
struct Esstials20CustomModifierView : View {
    var body: some View {
        VStack{
            Text("うちの果物大安売り")
                .modifier(Essentials20ViewModifier())
        }
        .padding()
    }
}
//11節で追加
struct Essentials20ViewModifier : ViewModifier{
    func body(content: Content) -> some View {
        content
            .padding()
            .foregroundColor(.pink)
            .font(.largeTitle)
            .background(Color.white)
            .border(Color.red,width: 2.5)
            .shadow(color: .orange, radius:10,x:0,y: 7)
    }
}
//12節で追加
struct Essentials20ClickableEventView: View {
    @State var menuNum = 0
    var body: some View {
        HStack{
            Button(action:clickableButtonMinus1){
                Text("-")
                    .foregroundColor(.white)
                    .frame(width: 44,height: 44)
            }
            .background(Color.green)
            .border(Color.red)
            .padding()
            
            Text("\\(menuNum)")
                .foregroundColor(.blue)
                .frame(width: 44,height: 44)
                .padding()
            Button(action:clickableButtonPlus1){
                Text("+")
                    .foregroundColor(.white)
                    .frame(width: 44,height: 44)
            }
            .background(Color.green)
            .border(Color.red)
            .padding()
        }
    }
    func clickableButtonMinus1(){
        menuNum -= 1
    }
    func clickableButtonPlus1(){
        menuNum += 1
    }
}
//12節で追加
struct Essentials20PrintEventView: View {
    var body: some View {
        Button(action: {
            print("タップされました!")
        }){
            Image(systemName: "tray.2.fill")
        }
    }
}
//13節で追加
struct Essentials20ContainerView: View {
    var body: some View {
        Text("乗り物集まれ")
            .font(.largeTitle)
            .fontWeight(.bold)
            .foregroundColor(.orange)
            .padding()
            .border(.orange)
        VStack(spacing:15){
            HStack(spacing:5){
                Text("🚗")
                    .modifier(Essentials20FontModifier())
                Text("🛵")
                    .modifier(Essentials20FontModifier())
                Text("🚲")
                    .modifier(Essentials20FontModifier())
            }
            HStack(spacing:5){
                Text("🚢")
                    .modifier(Essentials20FontModifier())
                Text("🚞")
                    .modifier(Essentials20FontModifier())
                Text("✈️")
                    .modifier(Essentials20FontModifier())
            }
        }
    }
}
struct Essentials20FontModifier: ViewModifier{
    func body(content: Content) -> some View {
        content
            .font(.system(size: 44))
            .padding()
    }
}
//13節で追加
struct Essentials20ContainerClosureView<Content :View>: View {
    let content:() -> Content
    init(@ViewBuilder content: @escaping()-> Content) {
        self.content = content
    }
    var body: some View {
        VStack(spacing: 15){
            content()
                .padding()
                .border(.brown,width: 3)
        }
        .font(.largeTitle)
    }
}

struct Essentials20ViewBuiltView:View {
    var body: some View {
        Essentials20ContainerClosureView{
            VStack{
                Text("おいしい果物をいろんな乗り物に乗せて全国にお届け🕺")
            }
        }
    }
}

struct Essentials20LabelView: View {
    var body: some View {
        VStack{
            HStack{
                Text("copyright")
                    .font(.caption)
                Label("Apple.Inc",systemImage: "apple.logo")
            }
            .font(.largeTitle)
            .foregroundColor(.gray)
            //レンダリング
            HStack{
                Text("copyright")
                    .font(.caption)
                Label(
                    title: { Text("Apple.Inc") },
                    icon: { Image(systemName: "apple.logo") })
                
            }
            .font(.largeTitle)
            .foregroundColor(.black)
        }
    }
}

"""

//タイトル
let essentialsChapter20NavigationTitle = "第20章"
let essentialsChapter20Title = "第20章 SwiftUIを使用したカスタムビューの作成"
let essentialsChapter20SubTitle = "第1節 SwiftUIを使用したカスタムビューの作成"

//ポイント
let pointEssentials20 = """
◾️ViewModifierでやるかどうかは自由なんだけど、
・微妙な調整が各ビューで必要な場合:普通のモディファイア
・各ビューを統一的に整えたい:ViewModifier
って感じで使い分けた方がいいかな〜〜〜って感じだね🧐

◾️ViewModifierやViewBuilderは、組み込むViewを用意する
👉単体ではビューではない
(当たり前の話だけどね👀💦)

"""
//URL
let urlEssentials20 = "https://note.com/m_kakudo/n/ndace411a9233"

struct Essentials20Contents: View {
    var body: some View {
        ScrollView{
            VStack{
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("第20章をやってるぜい🕺")
                //3節で追加
                Text("追加したぜい💃")
                Text("さらに追加ぜい🌱")
                //4節で追加
                HStack{
                    //5節で追加
                    Text("🍎ちゃん") + Text("大好き😍")
                    Text("🍊くん") + Text("好き❤️")
                }
                //6節で追加
                Esstials20FruitsView()
                //7節で追加
                Esstials20CarView()
                //9節で追加
                Esstials20TextModifierView()
                //11節で追加
                Esstials20CustomModifierView()
                //12節で追加
                Essentials20ClickableEventView()
                //12節で追加
                Essentials20PrintEventView()
                //13節で追加
                Essentials20ContainerView()
                Essentials20ViewBuiltView()
                //14節で追加
                Essentials20LabelView()
            }
            Text("🍇先輩、ちゃーっす")
                .padding()
        }
    }
}

#Preview {
    Essentials20Contents()
}
//6節で追加
struct Esstials20FruitsView : View {
    var body: some View {
        //HStackに変えるだけ
        HStack{
            Text("🍇先輩に呼び出されてきました")
            Text("🍈")
            Text("🍌")
        }
    }
}
//7節で追加
struct Esstials20CarView : View {
    var body: some View {
        HStack{
            Image(systemName: "car.fill")
            //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
            Text("車屋さん")
                .font(.largeTitle)
            //8節で追加
                .foregroundStyle(.green)
            Image(systemName: "car.fill")
            //8節で追加
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
        //8節で追加
        .lineSpacing(2)
        .padding()
    }
}
//9節で追加
struct Esstials20TextModifierView : View {
    var body: some View {
        VStack{
            Text("新鮮な果物屋")
                .font(.custom("Copperplate", size: 100))
                .fontWeight(.bold)
            //10節で追加(順番を入れ替え後〜〜)
                .padding()
                .border(Color.purple)
        }
        .padding()
    }
}
//11節で追加
struct Esstials20CustomModifierView : View {
    var body: some View {
        VStack{
            Text("うちの果物大安売り")
                .modifier(Essentials20ViewModifier())
        }
        .padding()
    }
}
//11節で追加
struct Essentials20ViewModifier : ViewModifier{
    func body(content: Content) -> some View {
        content
            .padding()
            .foregroundColor(.pink)
            .font(.largeTitle)
            .background(Color.white)
            .border(Color.red,width: 2.5)
            .shadow(color: .orange, radius:10,x:0,y: 7)
    }
}
//12節で追加
struct Essentials20ClickableEventView: View {
    @State var menuNum = 0
    var body: some View {
        HStack{
            Button(action:clickableButtonMinus1){
                Text("-")
                    .foregroundColor(.white)
                    .frame(width: 44,height: 44)
            }
            .background(Color.green)
            .border(Color.red)
            .padding()
            
            Text("\(menuNum)")
                .foregroundColor(.blue)
                .frame(width: 44,height: 44)
                .padding()
            Button(action:clickableButtonPlus1){
                Text("+")
                    .foregroundColor(.white)
                    .frame(width: 44,height: 44)
            }
            .background(Color.green)
            .border(Color.red)
            .padding()
        }
    }
    func clickableButtonMinus1(){
        menuNum -= 1
    }
    func clickableButtonPlus1(){
        menuNum += 1
    }
}
//12節で追加
struct Essentials20PrintEventView: View {
    var body: some View {
        Button(action: {
            print("タップされました!")
        }){
            Image(systemName: "tray.2.fill")
        }
    }
}
//13節で追加
struct Essentials20ContainerView: View {
    var body: some View {
        Text("乗り物集まれ")
            .font(.largeTitle)
            .fontWeight(.bold)
            .foregroundColor(.orange)
            .padding()
            .border(.orange)
        VStack(spacing:15){
            HStack(spacing:5){
                Text("🚗")
                    .modifier(Essentials20FontModifier())
                Text("🛵")
                    .modifier(Essentials20FontModifier())
                Text("🚲")
                    .modifier(Essentials20FontModifier())
            }
            HStack(spacing:5){
                Text("🚢")
                    .modifier(Essentials20FontModifier())
                Text("🚞")
                    .modifier(Essentials20FontModifier())
                Text("✈️")
                    .modifier(Essentials20FontModifier())
            }
        }
    }
}
struct Essentials20FontModifier: ViewModifier{
    func body(content: Content) -> some View {
        content
            .font(.system(size: 44))
            .padding()
    }
}
//13節で追加
struct Essentials20ContainerClosureView<Content :View>: View {
    let content:() -> Content
    init(@ViewBuilder content: @escaping()-> Content) {
        self.content = content
    }
    var body: some View {
        VStack(spacing: 15){
            content()
                .padding()
                .border(.brown,width: 3)
        }
        .font(.largeTitle)
    }
}

struct Essentials20ViewBuiltView:View {
    var body: some View {
        Essentials20ContainerClosureView{
            VStack{
                Text("おいしい果物をいろんな乗り物に乗せて全国にお届け🕺")
            }
        }
    }
}

struct Essentials20LabelView: View {
    var body: some View {
        VStack{
            HStack{
                Text("copyright")
                    .font(.caption)
                Label("Apple.Inc",systemImage: "apple.logo")
            }
            .font(.largeTitle)
            .foregroundColor(.gray)
            //レンダリング
            HStack{
                Text("copyright")
                    .font(.caption)
                Label(
                    title: { Text("Apple.Inc") },
                    icon: { Image(systemName: "apple.logo") })
                
            }
            .font(.largeTitle)
            .foregroundColor(.black)
        }
    }
}

以上。

さてと、

新聞とノーマン読んで今日もあとはアナログ生活に戻ろう!
残り少ないけど、
💃皆さんも良き週末を🕺

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