初心者プログラミング日記 SwiftUI②
//
// SandwichDetail.swift
// Landmarks
//
//
//SwiftUIはUIを定義する
import SwiftUI
//SwiftUIのViewの在り方
//SwiftUIではViewはViewプロトコルに準拠する構造体で基本クラスを継承しない
//つまり保存されているプロパティを継承せず、Viewはスタックに割り当てされて、位置情報等の値を渡される
//SandwichDetailにはサイズや重さだけが保存されていて、
//追加の割り当てや参照カウントはありません(たぶんUnitiyでいうUpdate()みたいな機能)
//SwiftUIは裏でViewの階層を積極的にまとめて、レンダリング用のデータを作る。
//そのためSwiftUIでは小さな単一目的のビューを自由に使える。
//サブViewの抽出に余計な作業がないためリファクタリング(プログラム内の整理)時に外部構造を考慮する必要が少ない
//SwiftUIはレンダリングのタイミングをプログラムの依存関係から把握している。
//状態変数とモデルが合わさってApp全体の単一の信頼できる情報源を構成する。
//情報源が複数にあると、実装忘れ等不具合の温床になるという考え方
//全てのプロパティは情報源かその派生値かに分類できる
//状態変数(State):Source of truth(get,set)
//変数(var) :読み取り専用派生値(get)
//binding :読み書き可能な派生値(get,set)
//定数(let) :読み取り専用のSource of truth
//ObservableObject :状態変数とモデルで構成されたApp全体のSource of Truth
//MVVM(Model View ViewModel)はコードデザインのパターン
//ModelとViewを分離することに主眼を置いている
//View :ユーザー接点(イベント) ー構造体
//Model :プログラム ー構造体
//ViewModel :ユーザー接点とプログラムを仲介するコード ークラス
struct SandwichDetail: View {
var sandwich: Sandwich //親Viewで渡される派生値(読み取り専用)
//状態変数_View実装内で読み書き変更可能
//状態変数を持つViewはViewの代わりに変数にストレージが割り当てられる
//SwiftUIは状態変数の読み取りや書き込みを観測できる
//変数の変更に伴いフレームワークは再度bodyを要求し、
//新たな状態値を用いてレンダリングを更新する
@State private var zoomed = false
var body: some View {
VStack {
Spacer(minLength: 0) //要素間の余白を維持するため自動的に最小になる
Image(sandwich.thumbnailName)
.resizable()
.aspectRatio(contentMode: zoomed ? .fill : .fit)
.onTapGesture {
//アニメーションを入れる
withAnimation{
zoomed.toggle()
}
}
Spacer(minLength: 0)
//もしスパイシーな場合ならバナーを表示
//拡大表示の場合もバナーは非表示
if sandwich.isSpicy && !zoomed{
//画面下部に背景がある下部バナーを作る
//SwiftUIではコンテンツに合わせて表示サイズが調整される
HStack {
Spacer()
Label ("Spicy", systemImage:"flame.fill")
.padding(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
Spacer()
}
//シンボルサイズも大きくなるので注意
//スモールキャップ(小文字サイズの大文字書体)もつける
.font(Font.headline.smallCaps())
.foregroundColor(Color.yellow)//文字色黄色
.background(Color.red)//単色背景
.transition(.move(edge: .bottom))//アニメーションをつける
}
}
.navigationTitle(sandwich.name)
//エッジまで表示
.edgesIgnoringSafeArea(.bottom)
}
}
struct SandwichDetail_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView{
SandwichDetail(sandwich: testData[0])
}
NavigationView{
SandwichDetail(sandwich: testData[6])
}
}
}
}
//SwiftUI:View自体が持続し依存関係を自動的に管理して、全てを最新かつ一貫性のある状態に保っている。
//適切な派生値を計算してバグが何度も発生しないようにする。
//Viewの更新を1つのメソッドに収集して単純化している。メソッドが1つなら呼び出せる組み合わせは1通り。
//bodyを唯一のエントリポイントにして、フレームワークにコード化している。(抽象化)
//サブViewの削除、Navigation Stackへのプッシュ、テーブルビューへの更新の実行など。
//View,App,Scene,bodyを含むその他のSwiftUIの抽象化が機能する。
//変化したUIの新しいインスタンスを単純に取り出すことで解決している。
//従来:Viewがデータを読み取るたびに依存関係になる。
//データの変更に応じてViewを更新して新しい値を反映する必要があるので、失敗すると何度もバグになる。
//複数の依存関係が複雑に発生すると、人が管理できる限界を越える。
//例)ズームすると拡大ボタンが表示→低解像度の画像が表示→ボタンを押す
//→機械学習操作がバックグラウンドスレッドに送られて画質を向上させる。
//イベントを把握するためのコールバックでバグが起きることが多くイベントの組み合わせの数だけ発生する
//とすればとても把握できるものではない。割り込みやアニメーション、並行処理まで入ってくると複雑さは未知数。
下部バナーアニメーションに利用したtransitionでは何が起こっているのか。
ズームによりバナーを削除すると、Viewは新しい位置のオフスクリーンにアニメーション化される。SwiftUIはアニメーション終了まで待機して階層からViewを実際に削除する。
そして戻った時にSwiftUIがオフスクリーンを挿入し、アニメーションで元の位置に戻す。
アニメーションを使用し階層構造からビューの追加や削除が簡単にできる。
このアニメーションは箱から出してすぐに双方向やりとりできる。
イベント駆動型(何かアクションがあったら動く)ではなく、データ駆動型(別のデータ連動で動く)の長所。
通常、OSには画面表示が行われないフレームバッファがありそれをオフスクリーンバッファという。オフスクリーンとはそのオフスクリーンバッファにレンダリングしていること。