プロパティラッパーとは
プロパティの値の読み書きに対して特定の処理を自動で適用する仕組みのことを言います。
今回はSwiftUIでよく使うプロパティラッパーを4つ紹介します。
今回紹介するプロパティラッパー
@State
@Binding
@ObservedObject
@StateObject
1,@State
@State
は画面の中で変わるデータを入れておく特別な箱のことです。
@State の特徴
- ビュー内のローカルな状態を管理する
-
@State
の値が変わると SwiftUI が自動で画面を更新 - 親ビューには渡せない(ローカル専用)
例えばゲームをやっているときにスコアが増えたりHPが減ったりすると思います。このような画面内のデータを管理しています。
import SwiftUI
struct CounterView: View {
@State private var count = 0 // 変わる数を入れる箱(最初は0)
var body: some View {
VStack {
Text("スコア: \(count)") // スコアを表示
.font(.largeTitle)
Button("+1する") {
count += 1 // ボタンを押したら count を増やす
}
.padding()
.font(.title)
}
}
}
上記のコードのように初期値として0を入れた箱を作ってボタンを押すごとに1が追加されていきます。
普通の変数 (var) を使うと、ボタンを押しても画面が更新されません。
@State
を使うと、SwiftUIが「値が変わった!」と気づいて、画面を自動で書き換えてくれます。
2,@Binding
@Binding
は親ビューが持つデータを子ビューで変更できるようにするための仕組みです。
@Binding の特徴
- 親ビューのデータを子ビューで操作できる
-
@State
などの 保持されているデータをバインドして操作可能 -
@Binding
だけではデータを保持できない(@State
などと組み合わせて使う)
今回はテレビとリモコンを例にして説明します。
テレビの音量はリモコンがないと設定できないものがほとんどだと思います。そのためリモコンで操作することによってテレビの音量を変更します。これを実際のコードに落とし込むと以下のようになります。
import SwiftUI
// 親ビュー(テレビ)
struct ParentView: View {
@State private var volume = 10 // 現状の音量(データ)
var body: some View {
VStack {
Text("テレビの音量: \(volume)") // 親ビューで音量を表示
.font(.largeTitle)
RemoteView(volume: $volume) // 子ビューに音量を渡す
}
}
}
// 子ビュー(リモコン)
struct RemoteView: View {
@Binding var volume: Int // 親の volume を参照するための @Binding
var body: some View {
HStack {
Button("🔈 -") { volume -= 1 } // 音量を下げるボタン
Button("🔊 +") { volume += 1 } // 音量を上げるボタン
}
.font(.largeTitle)
.padding()
}
}
このように親Viewで表示させているものをを子Viewで変更させるときに@Binding
を使います。
3,@ObservedObject
@ObservedObject
は、外からもらったデータを監視するための仕組みです。
@ObservedObject の特徴
- 親から渡された ObservableObject を監視する
- データが変更されると、自動でビューを更新
-
@StateObject
ではなく、@ObservedObject
は親ビューで作られたデータを使うために使用する
今回はサウナを例にします。サウナは温度が上がりすぎてもダメだし下がりすぎてもダメですよね。なので定期的に温度を監視する必要があります。
これをSwiftのアプリに落とし込むと以下のようになります。
import SwiftUI
// サウナの温度を管理するクラス
class Sauna: ObservableObject {
@Published var temperature = 80 // サウナの温度(デフォルト80℃)
}
// サウナスタッフ(サウナの温度を調整するビュー)
struct StaffView: View {
@ObservedObject var sauna: Sauna // サウナを監視
var body: some View {
VStack {
Text("サウナの温度: \(sauna.temperature)℃")
.font(.largeTitle)
HStack {
Button("加熱") { sauna.temperature += 5 } // 5℃上げる
Button("冷却") { sauna.temperature -= 5 } // 5℃下げる
}
.padding()
.font(.title)
}
}
}
このコードのように@ObservedObject
を使う SwiftUI の View が ObservableObject の変更を監視しています。
4,@StateObject
@StateObject
は、アプリの中で長く使うデータを管理するための箱のことです。
@StateObject の特徴
- 親ビューのデータを子ビューで操作できる
-
@State
などの 保持されているデータをバインドして操作可能 -
@State
は画面内でデータを管理しますが、画面を更新すると消えてしまいます@StateObject
では画面を更新されてもデータが残る
ポケモンを例にすると画面を更新するたびにポケモンのレベルがリセットされると困りますよね。1度上がったレベルというのはデータとして保持される必要があります。そういった必要な情報を管理するために@StateObject
が必要になります。
import SwiftUI
// ポケモンのデータを管理するクラス
class Pet: ObservableObject {
@Published var level = 1 // ポケモンのレベル(最初は1)
}
// ポケモンバック(親ビュー)
struct HomeView: View {
@StateObject private var myPoke = Pokemon() // ポケモンの箱
var body: some View {
VStack {
Text("ポケモンのレベル: \(myPoke.level)")
.font(.largeTitle)
Button("育てる") {
myPoke.level += 1 // レベルアップ!
}
.padding()
.font(.title)
}
}
}
もしポケモンのレベルを@State
で管理すると画面上でしか管理できません。@StateObject
を使ってポケモンのレベルアップがデータとして保存されます。
まとめ
私はこれまでUIkitで開発を進めていることが多く、UIkitではView間のデータ管理の処理を複雑に感じていました。プロパティラッパーを活用するとそれが容易にでき、非常に便利だと思っています。まだSwiftUIを本格的に初めて日が浅く、これから覚えることがたくさんあるのでその都度ここにアウトプットできればなと思います。