【じっくり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
}
}
てな感じのコードで〜〜〜
関数を使わないボタンビューの例
//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ビューを追加してカスタマイズしてくと
コードはこんな感じ
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)
}
}
てな感じに変更して〜〜〜
ここまでで言わなくても分かると思うけど
で紹介した、イメージとかを格納する
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)
}
}
}
以上。
さてと、
新聞とノーマン読んで今日もあとはアナログ生活に戻ろう!
残り少ないけど、
💃皆さんも良き週末を🕺