Swift Playgroundsで@Binding、dismissを学ぶ

SwiftUI

概要:

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と共有する、です。

Bindingの概念図

親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などをわかり易く解説してみる予定です。

ご意見、ご指摘等ありましたら、コメントを頂けると大変嬉しいです。

コメント

タイトルとURLをコピーしました