概要:
Binding、dismiss、sheetなどを学びます。
iPadのアプリSwift Playgroundsの「グリッドの編集」のソースコードで解説します。
Bindingの基本的な説明とsheet
前回ご説明した「グリッドの編集」アプリのファイル構成をもう一度見てみます。
この「グリッドの編集」の概要とファイル構成を理解されたい場合は、前回を先に読んで頂けると良いかと思います。
ここではメインViewのSymbolGridと、「+」を押した時のSymbol PickerのViewの遷移を見て見ましょう。
これもNavigationLinkを使っているのかな?
とおもいきや、縦にスクロースして下から出てきますね!
しかも戻るボタンでなく、シンボルを選んだだけで戻って、メインのViewにシンボルを追加しています。
この動きをコードで見ていきましょう。
以下はSymbolGridファイルの中で、SymbolPickerのViewの遷移と関係ある部分だけ抜き出してみました。
struct SymbolGrid: View {
@State private var isAddingSymbol = false
@State private var isEditing = false
@State private var selectedSymbol: Symbol?
// 省略
var body: some View {
VStack {
// 省略
}
// 省略
.sheet(isPresented: $isAddingSymbol, onDismiss: addSymbol) {
SymbolPicker(symbol: $selectedSymbol)
}
.toolbar {
// 省略
ToolbarItem( placement: .navigationBarTrailing) {
Button {
isAddingSymbol = true
} label: {
Image(systemName: "plus")
}
}
}
}
func addSymbol() {
guard let name = selectedSymbol else { return }
withAnimation {
symbols.insert(name, at: 0)
}
}
}
toolBarのボタンからsheetの起動
まず、先ほどアニメーションのように下から上がっている画面はsheetという仕組みが使われています。
sheetの記述本体は以下の3行だけです。
.sheet(isPresented: $isAddingSymbol, onDismiss: addSymbol) {
SymbolPicker(symbol: $selectedSymbol)
}
ここのisPresented:というプロパティにtrueを設定すると、設定したView(ここではSymbolPicker)が下から自動的に現れる仕組みになっています。
isPresentedに渡すプロパティisAddingSymbolというフラグは初期値falseで、ツールバーの「+」ボタンが押されるとtrueに設定され、sheetが自動的に起動される仕組みです。
SwiftUIはViewの遷移がほんと簡単ですね!
ところで、、、
なんでisAddingSymbolの前に”$”という記号が付いてるの?
検索で”swiftUI $”とかやっても、全くヒットしないし。。(笑)
当初の私の悩みでした。。
さあ、いよいよSwiftUIで非常に重要な要素の1つ、Bindingの解説に入ります。
Bindingの基本概念
Bindingの基本は、以下の図のように、
親Viewで定義した変数(プロパティ)を子Viewと共有する、です。
親Viewでの元のプロパティに@Stateを付ける。
子Viewでの参照プロパティには@Bindingを付ける。
親Viewから子Viewへの受け渡し時には変数の前に”$”を付ける。
上の図ではMatherViewで定義したtestMというプロパティを、MatherViewとChildViewのどこでも単一プロパティとして扱えるようになります。
Bindingのソースコード解説
では実際のソースコードを見ていきましょう。
まず親ViewであるSymbolGridの記述を見てみます。
struct SymbolGrid: View {
@State private var selectedSymbol: Symbol?
// 省略
.sheet(isPresented: $isAddingSymbol, onDismiss: addSymbol) {
SymbolPicker(symbol: $selectedSymbol)
}
selectedSymbolというプロパティを@State付きで定義していますね。それを”$”を付けてSymbolPickerに渡しています。
次に子ViewであるSymbolPicker側の記述です。
struct SymbolPicker: View {
@Binding var symbol: Symbol?
// 省略
ForEach(pickableSymbols) { symbol in
Button {
self.symbol = symbol
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: symbol.name)
// 省略
}
@Bindingを付けたsymbolというプロパティが定義されていますね。
親Viewから”$”を付けて渡されたselectedSymbolプロパティが、この子Viewではsymbolという名前のプロパティとして扱えるようになった、ということになります。
どれかのシンボル(ボタン)が押されると、ForEachから押されたシンボル(緑文字symbol)が渡されるので、SymbolPickerのsymbolプロパティ(青文字symbol)に代入されています。
同じsymbolとう名称なので混乱しないでくださいね!
その代入は即ち親ViewであるSymbolGridのselectedSymbolに代入されたことと等価になります。
sheet自体でもBindingが使われていますので、そちらも説明しておきます。
SymbolGridファイルの記述ですが、isAddingSymbolというプロパティを@State付きで宣言し、”$”付きでsheetのisPresentedに渡していますね。
struct SymbolGrid: View {
@State private var isAddingSymbol = false
// 省略
.sheet(isPresented: $isAddingSymbol, onDismiss: addSymbol) {
SymbolPicker(symbol: $selectedSymbol)
}
前に説明した通り、isAddingSymbolをtrueにしてisPresentedに渡すとシートが開いてSymbolPickerのViewを表示します。表示が閉じられる時、このisAddingSymbolはシステムでfalseに設定されて帰ってきます。
これも立派なBindingです。
ちなみにSymbolGridの記述の上の方にあるStepperでもBindingを使っています。
struct SymbolGrid: View {
// 省略
@State private var numColumns = initialColumns
// 省略
Stepper(columnsText, value: $numColumns, in: 1...6, step: 1) { _ in
withAnimation { gridColumns = Array(repeating: GridItem(.flexible()), count: numColumns) }
}
いかがでしょうか。Bindingの基本をiPad Playgroundsのソースコードを使って見てきました。
SwiftUIではBindingは普通に使われていますので、ぜひ、マスターしてください。
dismissの役割とコード説明
dismissはそのViewを破棄するイメージです。
今回のsheetで作ったSymbolPickerのViewは、担当の処理が終わったら自分自身でdismissを実行して消滅し、呼び出し先のSymbolGridのViewに遷移することをしています。
まずSymbolPickerファイルのdismissに関係するところを見て見ましょう。
struct SymbolPicker: View {
@Environment(\.presentationMode) var presentationMode
// 省略
ForEach(pickableSymbols) { symbol in
Button {
self.symbol = symbol
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: symbol.name)
.resizable()
.scaledToFit()
.symbolRenderingMode(.hierarchical)
.foregroundColor(.accentColor)
.ignoresSafeArea(.container, edges: .bottom)
}
.padding()
.buttonStyle(.plain)
}
@EnvironmentはViewのシステム環境の値などを読み出すことができます。
以下は環境から構造体presentationModeを取り出して、このViewの同じ名前にアサインしています。
@Environment(\.presentationMode) var presentationMode
構造体presentationModeはfunc dismiss()というメゾットを持っています。
それを実行しているのが以下の行です。
presentationMode.wrappedValue.dismiss()
なんで”wrappedValue”とかが間に挟まってるの?
とか突っ込まれると、話が長くなり、100%説明しきる自信がないです。。(笑)
最近この使い方は「非推奨(Deprecated)」になってしまいましたので、
ここでは動作の概略を掴むのところまでに留めておきましょう(汗)
新しく推奨されている表記と併記すると以下のようになります。
@Environment(\.presentationMode) var presentationMode // 非推奨
@Environment(\.dismiss) var dismiss // 推奨
presentationMode.wrappedValue.dismiss() // 非推奨
dismiss() // 推奨
この2箇所を非推奨から推奨に書き換えて動作することを確認できます。
ずいぶんスッキリしましたね!
毎年進化していくところも、SwiftUIの良いところと思っています。
onDismiss
もう1つだけ説明しておきたいところがあります。親View側のSymbolGridファイルに戻りましょう。
.sheetの( )の中にonDismissというのがあります。
文字通り、遷移先のViewでdismissされて戻ってきた時に、addSymbolという関数(メゾット)を呼ぶという記述です。
struct SymbolGrid: View {
// 省略
var body: some View {
VStack {
// 省略
}
// 省略
.sheet(isPresented: $isAddingSymbol, onDismiss: addSymbol) {
SymbolPicker(symbol: $selectedSymbol)
}
// 省略
}
func addSymbol() {
guard let name = selectedSymbol else { return }
withAnimation {
symbols.insert(name, at: 0)
}
}
}
addSymbolメソッドは、SymbolPickerで選択したsymbolを、このSymbolGridのSymbolsという配列の先頭に追加しています。
これで一連の動作が理解できましたね。
1. SymbolGrid で「+」が押されるとisAddingSymbolフラグがtrueにセットされる。
2. isAddingSymbolのtrueはisPresentedに渡されsheetが起動する。
3. sheetは設定されたViewであるSymbolPickerを表示する。
4. Symbolzpickerでは選ばれたシンボルをBindingされたプロパティに代入し、自分をdismissする。
5. SymbolGridではaddSymbol関数が呼ばれ、シンボルをsymbols配列に加える。
いかがでしょうか。SwiftUIのエッセンスとも言えるBindingやdismissを、「グリッドの編集」のソースコードで見ることができました。
最後に
2回に分けてiPadのSwift Playgroundsの「グリッドの編集」を解説してみました。
NavigationやBinding、dismissなどView間の遷移に使われる重要な要素が、ソースコードの中で学ぶことができました。
最後まで読んで頂いて、ありがとうございます。
次回は「ジェスチャの認識」でTapGestureなどをわかり易く解説してみる予定です。
ご意見、ご指摘等ありましたら、コメントを頂けると大変嬉しいです。
コメント