【じっくりSw1ftUI33】実践編3〜第18章 SwiftUIアーキテクチャ〜SwiftUIの階層構造を知ろう👀
さてと、今週もよろしく!
前回は、
でXcodeの基本的な機能に触れたので〜〜〜、今回は、
SwiftUI アーキテクチャ
で、SwiftUIアプリがどんな構造になっているかを読んでく〜〜〜!
「そんなもん作っていく中が肌感覚で身につくから大丈夫」
なんて声が、万年ど素人みたいにバグや障害ばかり起こす
自称、プロのエンジニア
から聞こえてきそうだけど、そーゆー人ほど、
で書いてる
コンストラクション
を理解しようともせず、目先の機能の追加とか改修で部分的にしか見ずに、
コードとかカラムの変更なんかを安易に繰り返し、実証テストもしない
👉パブリッシュとかリリースした段階で、予想外のバグが起きまくる
しかも、
でも書いた
エラー拒否症候群
で想定外のエラーが起きた瞬間に頭が真っ白になり、何をしていいかわからない、しかもしっかり理解してないし、自信もなく、自分で全てを解決しないといけない=他人に頼る、相談するは悪って勘違いしてるから、
でつぶやいてる
と真逆なことをやり、5年以上数十年、開発現場にいるのに、
同じ歴史を繰り返す。
さらに、
👉ITの先には常に人がいる
ことすら知識としては知ってるつもりになっていても、肌感覚で理解しようともしてないから、
ホント、安易に目先の改修をやみくもにやって、
今日もバグと障害ばかり起こして、
余計な残業とか休日出勤を増やしてることにすら気づいてない
からね〜〜〜〜
そんな人はどこで最初に差がついてるかと言えば、
自分で好きでやってる趣味の本の学びすら、目先の生産性とか効率ばかりを気にして、「こんなこと別に読まなくても、知っていて当たり前」って即断即決で判断して読み飛ばし、実はプログラミングの勉強を始めた当初から同じことをやってるから、実はこーゆーページ数が短くて、一見、読んでも意味がなさそうなところ(=自分の目先の興味や関心が薄いところ)を、
一回もじっくり読んでしっかり理解しようとしたことがなかった
ってことに尽きるんだけどね。
システムにしろアプリにしろWEBサイトにしろ、
他のページとかビュー、DBなんかで裏側は繋がってる
から
コンストラクション=全体的な構造
アーキテクチャ=階層的な構造
を理解して、
今自分は、全体からするとどの部分を作っている/改修してるってイメージを持つことが実際に作る前の作法としてめちゃくちゃ大事
なのに、せっかちな素人ほど入口の時点で、よくわかりもしてないのに、
「今の自分には関係ない」って即断即決ですっ飛ばす👀💦
そーゆー素人ほど、すぐに目先の成果を求めがちで、偉大な習慣を身につけようとしない
型破りと形無しの違いすら分かろうとしない
+
一度身についた習慣を自称プロエンジニアほど、改めようとしない
からね。
いかにアーキテクチャとかコンストラクションを意識することが重要か
については理解してもらえたと思うので〜〜〜
早速、第18章を読んでく🕺
「オイラの学び直しの記録なんざ関係ない」って人はいつもどおり
で公開されてるみたいだからそちらでどうぞ〜〜〜〜
じっくり第18章を読んでく
概要だと、SwiftUIのアプリの構造を、特にキーになる要素である
App(オブジェクト)
Scene(オブジェクト)
View(オブジェクト)
を強調して解説してく
って書いてんね👀
まずは階層構造を知ろう
ここはさっき出したリンクの図を前回同様、流用するけど
アプリ製作ではビューを作ることが一番多いし、Viewがないと始まらないんだけど、Viewは、Viewを統合するSceneに管理されてて、Sceneは大元でApp自体に乗っかってるってことがここでわかるね👀つまり、(当たり前の話なんだけど)
Viewは、(Sceneを介して)Appと繋がってる
じゃ、それぞれどんな役割なんだ👀って話だけど
App
SwiftUIストラクチャーの最上位レベルのオブジェクト
ランチャーとかライフサイクルを制御してる。
それぞれのSceneを管理する。
ユーザーインターフェース(操作画面)を作り出す。
インスタンスも含む。
Scene
単数または複数のSceneを含むSwiftUIアプリにおいて、Sceneはアプリケーションのユーザーインターフェースのセクションまたは領域を示す。
iOSやWatchOSは典型的で、スクリーン全体をウィンドウフォームで、MacOSとiPadOSは複数のスクリーンで構成される。
例えば、異なるSceneを持つ場合、ダイアログを含んだ複数のタブで選択させたり、複数ウィンドウが存在するといったコンテキスト固有のレイアウトを含む。
アプリをデザインしようとする時にSwiftUIに含まれている建て付けで備わってる原初的なSceneが使われることが多く、その最も一般的なのはWindowGroupとDocumentGroup。
ま、自分でカスタマイズしてScene自体を作ることもできるけどね👀
View
ボタンやラベル、テキストフィールドのような操作画面の視覚要素を作る最も基本的なブロック。
各シーンは操作画面の各セクションを作るビューの階層を含む。
例えば、VStackビューは、バーティカルレイアウトの中で子ビューを表示するようにデザインされてる。
SwiftUIのアプリを作る際に、ビューを自分でカスタマイズしたビューも作れる。
カスタムしたビューは、アプリケーションのユーザー インターフェイスの要件を満たし、ビューの外観と動作をカスタマイズした他のビューのグループで構成される。
ボタンとテキストビューを組み合わせたでVStackビューをSceneまで含んだ階層で示すと下図な感じ👀💦
まとめ
ここではあくまでも、
Appの下にSceneがあって、Sceneの下にViewがあるてことをイメージする
で充分なんだけど、、、。
Viewで普段作業してく中で、単純にViewだけを作っても、
そのViewで変更したプロパティなんかを、
でやった
プロパティラッパー
で他のViewに受け渡す際に、
Sceneもちゃんと管理しないと、うまく想定どおりに動かない
なんてことが出てくるから、
単純にViewで使うメソッドとかモディファイアばかりを丸暗記したらダメ🙅
かと言って、どこかの記事なんかで何回も変な解説になってるのを見たことあるけど、
ViewとSceneはワンセットで
いつもWIndowGroupに追加しないといけないんだ
みたいに曲解してもダメ🙅
実際、オイラが今作ってるコードテンプレート
のコード見てもらったらわかると思うけど、オイラはこれまででは必要なかったから、
Sceneの操作を解説したことなんてまだ一度もないからね
👉Sceneの操作が必要な時だけSceneにコードを書けばいい
=じゃあどんな時にSceneの操作まで必要なViewは何か
その勘どころを実際に作って動かしながら理解していくことが大事
ってことが言いたいだけだからね。そこをよく理解して身につけもせずに、Viewの作りだけサンプルコードだけ見て、実際に動かしもせずに理解した気になって、頭の中でこのコードで動くなんて想定して実際に動かしもせずにいくらやったところで
アプリが期待した通りに動かずに涙目😢
そして今日も残業とか持ち帰りの自己研鑽で徹夜
なんてことになるからね。
養老孟司さんの「脳化社会」か何か知らないけど、
サンプルだけ見て頭の中(=脳)だけでわかった気になる
のと、
実際に作りたいしっかり想定どおりに動く物を作れるようになる
のは、
全然違う!
これはSwiftUIに限らず、マイクロソフトオフィスも含めた全てのソフトウェアで言えることだけど、駆け出しさんとか資格だけを持ってる人なんかで、
教科書とか参考書なんかを読んで単体のViewや機能までは作れるようになるんだけど、単体を繋いでひとつのシステムとかアプリを作る=結合させようとした途端に、いきなり難しくなって、
手が止まる
諦める
どうしていいかわからない
なんてことはザラだし、そこで初めて自分が本当の意味で理解をしていなかったことに気づく自称、プロエンジニアさんは結構多いし、それまでは「べき論」とか根拠のない自信で、先人のゆーことに耳を傾けなかったのに、少し難しくなった途端に、
人を頼るしかないのに、
それまでの行いが悪すぎたり変なプライドが邪魔をして、
ドツボにハマる、沼になる
で、結果、業界自体から退場していく
なんてエンジニアさんを星の数ほど見てるからね。本やWEBなんかに載ってる単体のサンプルコードだけをやって理解した気になってるのは、
まだ簡単なこと=入り口にしか立っていない
から、
実は理解どころか、実務で使える技能の習得=努力にすらなっていない
ってことをすごく近い将来に知ることになる。教科書以上のちょっと難しそうなことすらやっていないんだから、
目的にあった行動=努力にすらなっていないでしょ👀?
裏を返すと、ただ本に書いてるコードに触れて、
理解したとか自分は何でも作れるなんて錯覚してるだけ
ま、だから、オイラは自分のプロフィールで
ちょい難とダラダラ
って書いてたりするんだけどね〜〜〜。
ま、とりあえずこの記事では、
結合しようとした途端に、本当に理解してるかが始まる
って意識してもらえたら充分。
iBooksだとここの章はたった3ページなんだけど、
ただ読むだけではなく、いろんなよもやま話を盛り込むとこれだけ長くなるし、それだけでも
本当の意味で理解するってことが
如何に、時間がかかることかわかるでしょ👀❓
頭で理解した気になってこの章を読み飛ばし、
App-Scene-View
の繋がりすら分からないままに、いざ自分でアプリを作ったのにプロパティラッパーもおそろかにしてるから、
ああでもない、こうでもない
ってやみくもな試行錯誤とむやみなググリングだけで、しっかり理解しながら進んだ人が数分で作れる作業に、
数十倍の時間(丸1日とか1週間)をかける
上に結局、
作れない
挫折する
簡単なはずなのに簡単にできないSwiftの作りがおかしい
なんて言い出してたら、
ちゃんと読んで理解すれば簡単にできるものを自分で難しくして、
Swiftに文句まで言ってんだったら
愚の骨頂、バカの極みでしょ
(ま、他の言語の開発現場でそんな人山ほど見たことあるけどね👀💦)
それはただ単純に、
基本とか当たり前を疎かにして目先にせっかちに反応しか出来てない
自分が悪いだけの話
だし、ま、他の過去の記事でも何回も書いてるけど、だから本当の職人さんほど
どこまでも基本を疎かにしない
基本にあくまでも忠実
そんな当たり前のことを素直に当たり前にやる
から職人さんなんだけどね。そんな方々が、自分のことを
プロ
なんて言わないしね。言う必要もないからね〜〜〜。
タイパ・コスパとかばっかりゆーてる人ほど、ま、そーゆーご時世なんだろうけど、
自称プロが多いし、数字が大好き
なんだけど、
理解にかける数分〜数時間を惜しんで、目先の時間を疎かにして、
生半可なプライドだけで、その数十倍の時間をかけて結局できないなら、何の意味もないし。
それを生産性とゆーのかしら?🧐
足し算と引き算すらできない幼稚園児と変わらないってことを自分で周囲に露呈してるだけなのに。。。
人間の理解する工程に時代やトレンドが関係あるのかしら?🤔
さてと次回は、
いよいよ20章から実際にSwiftUIビューを構築してく最後の前段階、
基本的な 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
}
//各項目に表示する文字列
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),
]
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)
})
}
}
}
#Preview {
iOSApp17DevelopmentEssentials()
}
Essentials18.swift
import SwiftUI
import WebKit
//iOSApp17DevelopmentEssentialsCh18.swift
//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentialsCh18: Identifiable {
var id: Int
var title: String
var view: ViewEnumiOSApp17DevelopmentEssentialsCh18
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentialsCh18 {
case Sec1
}
//各項目に表示するリスト項目
let dataiOSApp17DevelopmentEssentialsCh18: [ListiOSApp17DevelopmentEssentialsCh18] = [
ListiOSApp17DevelopmentEssentialsCh18(id: 1, title: essentialsChapter18SubTitle, view: .Sec1)
]
struct iOSApp17DevelopmentEssentialsCh18: View {
var body: some View {
VStack {
Divider()
List (dataiOSApp17DevelopmentEssentialsCh18) { data in
self.containedViewiOSApp17DevelopmentEssentialsCh18(dataiOSApp17DevelopmentEssentialsCh18: data)
}
.edgesIgnoringSafeArea([.bottom])
}
.navigationTitle(essentialsChapter18NavigationTitle)
.navigationBarTitleDisplayMode(.inline)
}
//タップ後に遷移先へ遷移させる関数
func containedViewiOSApp17DevelopmentEssentialsCh18(dataiOSApp17DevelopmentEssentialsCh18: ListiOSApp17DevelopmentEssentialsCh18) -> AnyView {
switch dataiOSApp17DevelopmentEssentialsCh18.view {
case .Sec1:
return AnyView(NavigationLink (destination: Essentials18()) {
Text(dataiOSApp17DevelopmentEssentialsCh18.title)
})
}
}
}
#Preview {
iOSApp17DevelopmentEssentialsCh18()
}
//Essentials18.swift
struct Essentials18: View {
var body: some View {
VStack{
TabView {
Essentials18Points()
.tabItem {
Image(systemName: pointImageTab)
Text(pointTextTab)
}
Essentials18WEB()
.tabItem {
Image(systemName: webImageTab)
Text(webTextTab)
}
}
}
}
}
#Preview {
Essentials18()
}
struct Essentials18Points: View {
var body: some View {
ScrollView{
Text(pointEssentials18)
}
}
}
#Preview {
Essentials18Points()
}
struct Essentials18WebView: 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 Essentials18WEB: View {
private var url:URL = URL(string: urlEssentials18)!
var body: some View {
Essentials18WebView(searchURL: url)
}
}
#Preview {
Essentials18WEB()
}
//タイトル
let essentialsChapter18NavigationTitle = "第18章"
let essentialsChapter18Title = "第18章 SwiftUIアーキテクチャ"
let essentialsChapter18SubTitle = "第1節 SwiftUIアーキテクチャ"
//コード
//ポイント
let pointEssentials18 = """
Viewは、(Sceneを介して)Appと繋がってる
◾️App
⒈SwiftUIストラクチャーの最上位レベルのオブジェクト
⒉ランチャーとかライフサイクルを制御してる。
⒊それぞれのSceneを管理する。
⒋ユーザーインターフェース(操作画面)を作り出す。
⒌インスタンスも含む。
◾️Scene
⒈単数または複数のSceneを含むSwiftUIアプリにおいて、Sceneはアプリケーションのユーザーインターフェースのセクションまたは領域を示す。
⒉iOSやWatchOSは典型的で、スクリーン全体をウィンドウフォームで、MacOSとiPadOSは複数のスクリーンで構成される。
⒊例えば、異なるSceneを持つ場合、ダイアログを含んだ複数のタブで選択させたり、複数ウィンドウが存在するといったコンテキスト固有のレイアウトを含む。
⒋アプリをデザインしようとする時にSwiftUIに含まれている建て付けで備わってる原初的なSceneが使われることが多く、その最も一般的なのはWindowGroupとDocumentGroup。
⒌ま、自分でカスタマイズしてScene自体を作ることもできるけどね👀
◾️View
⒈ボタンやラベル、テキストフィールドのような操作画面の視覚要素を作る最も基本的なブロック。
⒉各シーンは操作画面の各セクションを作るビューの階層を含む。
⒊例えば、VStackビューは、バーティカルレイアウトの中で子ビューを表示するようにデザインされてる。
⒋SwiftUIのアプリを作る際に、ビューを自分でカスタマイズしたビューも作れる。
⒌カスタムしたビューは、アプリケーションのユーザー インターフェイスの要件を満たし、ビューの外観と動作をカスタマイズした他のビューのグループで構成される。
"""
//URL
let urlEssentials18 = "https://note.com/m_kakudo/n/n587f8bf6064d"
以上。
さてと、新聞とノーマン読も!!
みなさんも良い週末を〜〜〜〜🕺