SwiftUIデータフロー入門
驚くほどの少ないソースコードでUI画面を表示できるSwiftUIは、データの扱いも独自です。
今回はデータ(状態)の重要なしくみである @State と @Binding を詳しく解説します。
【2020年8月6日追記:StateとBindingの公式ドキュメント(英文)が詳しくなっていました】
※この記事ではSwift言語(最新の5.1)の基本的な知識を前提にしています。コード内のキーワードや書式などの不明点は Swift5初級ガイドなどを参照してください。
毎月札幌でiOSアプリ作りをアシストするセミナーをやっています。1時間にわたるセミナーの全内容を、物理的に参加できない方のためにnote上で公開します。
お知らせ
電子書籍『Swift5初級ガイド』をAppleのブックストアから出しました。サンプルは無料です。MacでもiPadでもiPhoneでも読めます。
Swift 5.3に対応した第6版がダウンロード可能です。(ご購入済みの場合は無料アップデートです)
ブックストアから一度購入すると今後のアップデートは無料で読めます。
iOSアプリ作りをアシストするセミナーは今後も月一回のペースで続ける予定です。(2020年3月以降COVID-19感染拡大防止のため休止しています)
詳細は connpass.com の 札幌Swift でご確認ください。そして機会があればぜひ参加してください。
アプリ作りやプログラミング教育に関連する話題は 札幌Swift のfacebookページ で発信しています。
・画像クリックで拡大表示できます
・画像を拡大表示中は画像の左右をクリックで画像だけを順に表示できます
・ソースコード部分は左右にスクロールできます
Xcode は 11.2.1 の画面が中心です、サンプルは Xcode 11.3で確認しています。
1 SwiftUIのおさらい
圧倒的に簡潔なコードで画面を作れるSwiftUIの最大の特徴は宣言型シンタックスです。
SwiftUIフレームワークは矛盾のない、美しく正確なユーザーインターフェースを記述できるよう、ゼロから構築されています。
宣言型なので、基本的に画面表示・更新処理のための関数は使いません。
SwiftUIはフレームワークの名前でもあります。
SwiftUIフレームワークの型名などには接頭辞がありません。
UIKitフレームワークならUIButtonなど『UI』ではじまっていました。
SwiftUIの型は「Button」のように一般の単語だけです。
1-1 View は struct
SwiftUIフレームワークではビューは class ではなく struct です。
これはたまたまではなく、UIKit など従来の class を継承して構築するフレームワークの欠点を避け、簡潔なコードで動作するための変更です。
SwiftUIの View についてはは『SwiftUIの文法 その1 View』を参照してください。
Swift では class は参照型で struct は値型です。
この違いは SwiftUI でも重要です。
1-2 structの特徴
Swift言語の struct はメソッドを持つことができます。
classと似ていますが、classのように継承はできません。
参照型では一つの実態を複数から参照します。
参照型インスタンスのプロパティを変更すると、参照しているすべてで影響を受けます。
値型は変数に代入するとコピーされるので、変更の影響は原理的にほかに広がる心配はありません。
値型である struct ではプロパティを変更するメソッドには mutating キーワードが必要です。
SwiftUIのビューではmutatingキーワードを注意深く避けています。このおかげで再表示などが「軽く」なっているとWWDCのセッションで説明しています。
structは継承はできませんが、プロトコルを使うことができます。
共通する性質や挙動をプロトコルとしてまとめています。
プロトコルに準拠するだけでなく、実装が必要な点は継承とは違います。
1-3 独自のデータはプロパティ
structでも独自のデータはプロパティで持ちます。
SwiftUIでは状態のように変化する場合 var宣言で変数にするだけでなく、@State属性を付けたプロパティを使います。
structのメソッドで mutating キーワードなしにプロパティを変更できません。
注意:@Stateを使わずにmutating付きメソッドで状態を変更するのは SwiftUI のやり方では使いません。
2 @State 属性
2-1 Swift の属性
アットマークではじまる属性(Attributes)は、宣言または型に関する付加情報を提供します。
Swift言語でもいくつかの属性が決められています。
Objective-C由来の属性を除き、Swift言語で決められている属性は小文字ではじまります。
一方カスタム属性は型をもとにしているため大文字ではじまります。(Swiftでは型は大文字ではじまるためです)
2-2 @State はSwiftUIのカスタム属性
@State と @Binding は後から説明するプロパティ ラッパーの機能を利用してSwiftUIに追加されたカスタム属性です。
@State と @Binding についてはWWDC2019のセッション226『Data Flow Through SwiftUI』の前半で詳しく解説しています。
@State属性はビューの状態を扱うために使います。
サンプルコードです。
// sample12-01 @Stateの例
struct SampleView: View {
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(isPlaying ? "再生中" : "停止")
.font(.title)
.foregroundColor(isPlaying ? .red : .gray )
Button(action: {self.isPlaying.toggle()}) {
Text("Play")
.font(.title)
}
}
}
}
// playgroundで実行する場合に必要なコード
PlaygroundPage.current.liveView = UIHostingController(rootView: SampleView())
Playボタンクリックでテキスト表示が「停止」と「再生中」交互に切り替わるサンプルです。
Text(isPlaying ? "再生中" : "停止") の部分は
isPlayingがtrueなら"再生中"、
falseなら"停止"を返す三項演算子です。
文字色の部分も同様でisPlayingの状態で赤とグレイを切り替えています。
Button(action:で始まる行は Button型のイニシャライザ
init(action: @escaping () -> Void, @ViewBuilder label: () -> Label)
の label: 部分をトレイリングクロージャで書いたコードです。
action:が @escaping属性付きクロージャなので {self.isPlaying.toggle()} のプロパティはselfの明示が必要です。
WWDCセッション226ではクロージャであることを強調した改行がありました。
Button(action: {
self.isPlaying.toggle()
}) {
サンプルのように一行に書くことができます。どちらも機能は同じです。
タイトルがPlayのボタンをタップするとisPlayingが反転されます。
実行するとPlayボタンクリックで「停止」が「再生中」に切り替わります。
VStackについては『SwiftUIの文法 その1 View』を参照してください。
2-3 @Stateのはたらき
SwiftUIは状態などを同期不要にコーディングできるようにゼロから作られました。
状態の変化を@Stateで扱い、@Bindin属性を使って別のビューに状態を結び付けます。
sample12-01 でisPlayingプロパティに@State属性が使われています。
@State private var isPlaying: Bool = false
の基本は var isPlaying: Bool です。
Bool型のプロパティに初期値としてfalseを与えています。
しかし @State を付けると単なるBool型変数ではなくなるのです。
@Stateが直接は見えない処理を自動で行うようにBool型を『ラッピング』してくれます。
@Stateは(structである)ビューのプロパティを『状態』の変化に対応できるようにし、状態が変化した時にはビューを再表示します。
@Stateがラッピングする処理はプロパティが定義された型内だけで使われることを前提にしています。
このため外部からアクセスされることのないように private の指定も必要になります。
2-4 再表示も自動
上記のサンプルコードではボタンのアクション {self.isPlaying.toggle()} でisPlayingの状態を反転させています。
そのほかにisPlayingプロパティを監視している部分はありませんが、@State属性付きのプロパティが変更されると、ビューは自動的に再表示されます。
再表示用のメソッドは特にありません。
bodyプロパティのゲッターが呼ばれ、結果として最新状態で再表示されます。
WWDCでは『変化があった部分だけが再表示されるため、効率は良い』との解説がありました。
2-5 SF Symbolsをアイコンとして利用
次にサンプルコードのPlayボタンをWWDCのセッション226のようにアイコン表示に変更しましょう。
ボタンのラベルをTextからImageに変更します。
// sample12-02 アイコンボタン
import SwiftUI
import PlaygroundSupport
struct SampleView: View {
@State private var isPlaying: Bool = false
// 注:.font(.system(size: 100, weight: .bold))はダイナミックタイプに対応しない
var body: some View {
VStack {
Text(isPlaying ? "再生中" : "停止")
.font(.title)
.foregroundColor(isPlaying ? .red : .gray )
Button(action: {self.isPlaying.toggle()}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
.font(.system(size: 100, weight: .bold))
.padding()
}
}
}
}
// playgroundで実行する場合に必要なコード
PlaygroundPage.current.liveView = UIHostingController(rootView: SampleView())
プレイボタンアイコンにはSF Symbolsを使っています。
今後も記事を増やすつもりです。 サポートしていただけると大変はげみになります。