SwiftUI では、Swift5.1で導入された新機能がたくさん使われているので、ここでよく使われるものをピックアップします。
Opaque Return Type
戻り値の型を抽象的に記述する方法。戻り値の前に「some」をつけます。以下はSwiftUIでよく見られる記述です。
var body: some View {
...
}
bodyは1つのViewを返すプロパティですが、そのViewの種類はTextだったりVStackだったりさまざまなので、someが使われています。
Implicit Return
関数や計算型プロパティで、式が1つしかない場合は return キーワードが省略可能になりました。SwiftUI の body プロパティも return が省略されています。
Property Wrapper
構造体のプロパティをラップして、独自の setter/ getter 処理を定義できます。
@propertyWrapper
struct 構造体名 {
var wrappedValue: 型 {
get { ... }
set { ... }
}
}
上記のような構造体を定義して、ラップするプロパティに @構造体名
をつけます。構造体なので、イニシャライザで引数を渡すことも可能です。以下は UserDefault の構造体をラップした場合の例です。
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
このラッパーを使用するときには @UserDefault
をつけます。
struct UserDefaultsConfig {
@UserDefault(key: "name", defaultValue: "")
static var name: String
@UserDefault(key: "age", defaultValue: 20)
static var age: Int
}
print(UserDefaultsConfig.name) => ""
print(UserDefaultsConfig.age) => 20
SwiftUIではこの仕組みを使っていくつかプロパティラッパーが用意されています。
State
@State をつけて宣言された変数の値が変更されると、自動的にViewの更新が走ります。
struct ContentView: View {
@State var text: String = "1"
var body: some View {
VStack {
Text(text)
Button(action: {
self.text = "2"
}) {
Text("2にする")
}
}
}
}
上記でボタンを押すと自動的に"2"が表示されます。
Binding
@State で定義されたプロパティを他のViewで共有します。たとえば親子関係のView同士で共有する場合、親で@Stateプロパティを宣言して、子に渡すときに$つきの変数(「射影プロパティ」といいます)で値を渡し、さらに子で@Bindingをつけて変数を定義します。
struct ParentView: View {
@State var text: String = "1"
var body: some View {
VStack {
Text(text)
Button(action: {
self.text = "2"
}) {
Text("2にする")
}
ChildView(text: $text)
}
}
}
struct ChildView: View {
@Binding var text: String
var body: some View {
VStack {
Text(text)
Button(action: {
self.text = "3"
}) {
Text("3にする")
}
}
}
}
親のViewでボタンの押しても、子のViewでボタンを押しても両方のViewが同時に更新されるようになります。
ObservedObject
任意のクラスのプロパティが変更されたときにビューを更新します。@State
では1つのプロパティのみ監視可能でしたが、これで複数のプロパティを監視できるので、監視したいプロパティをクラスにまとめたりすることができます。
任意のクラスにはObservableObject
プロトコルを実装し、監視対象のプロパティには@Published
をつけます。
Viewの生成時に任意のクラスのインスタンスを渡してあげましょう。各 View でボタンを押すと @published をつけたプロパティの値が変更され、他のViewで自動的に更新が走ります。
struct ParentView: View {
@ObservedObject var data: UserData
var body: some View {
VStack {
Text(data.name)
ChildView(data: data)
ChildView2(data: data)
}
}
}
struct ChildView: View {
@ObservedObject var data: UserData
var body: some View {
VStack {
Text(data.name)
Button(action: {
self.data.name = "ChildView"
}) {
Text("更新する")
}
}
}
}
struct ChildView2: View {
@ObservedObject var data: UserData
var body: some View {
VStack {
Text(data.name)
Button(action: {
self.data.name = "ChildView2"
}) {
Text("更新する")
}
}
}
}
class UserData: ObservableObject {
@Published var name: String = "View"
}
struct ParentView_Previews: PreviewProvider {
static var previews: some View {
return ParentView(data: UserData())
}
}
EnvironmentObject
複数のViewでプロパティの状態を共有する際に、階層構造に従って親から子に順にデータを渡さなくても、任意の階層でデータを参照することができます。
まず親のインスタンスを生成するときに environmentObject
モディファイアで共有するデータを渡します。こうすると、そのViewと階層構造にあるViewでデータを共有することができます。
そして、そのデータを参照したいViewで @EnvironmentObject
をつけてそのクラスを宣言します。
ObservedObject の時のようにViewの生成時にデータクラスのインスタンスを渡す手間が省けます。
struct ParentView: View {
var body: some View {
ChildView()
}
}
struct ChildView: View {
var body: some View {
GrandChildView()
}
}
struct GrandChildView: View {
@EnvironmentObject var data: UserData
var body: some View {
Text(data.name)
}
}
class UserData: ObservableObject {
@Published var name: String = "View"
}
struct ParentView_Previews: PreviewProvider {
static var previews: some View {
let data = UserData()
return ParentView().environmentObject(data)
}
}
Environment
Viewの設定値を参照・操作するのに使用します。
SwiftUI では、フォントやロケールなど、Viewを生成するのに使用するさまざまな環境値(EnvironmentValues)があります。
EnvironmentValues はViewの生成時に enviorment
メソッドで値を設定することができ、参照するには以下のように @Environment(\.key)
をつけます。
struct ParentView: View {
@Environment(\.locale) var locale: Locale
var body: some View {
Text(locale.description)
}
}
struct ParentView_Previews: PreviewProvider {
static var previews: some View {
return ParentView().environment(\.locale, Locale(identifier: "ja_JP"))
}
}
Function Builder
改行で区切られたコードを、あるメソッドの引数と渡して処理できる機能です。VStack {} や HStack {} のブロック内では、Viewを改行区切りで定義していきますが、これらは Function Builder 機能で実際には VStack で定義された別のメソッドの引数として渡され処理されています。
Function Builder を使うには、@_functionBuilder
をつけて構造体を定義し、static な buildBlock
メソッドで引数と処理を書きます。
使用する際は @構造体の名前
をつけてメソッドを定義します。
@_functionBuilder
struct StringBuilder {
public static func buildBlock(_ a: String, _ b: String) -> String {
a + b
}
}
@StringBuilder func combineStrings() -> String {
"田中"
"太郎"
}
print(combineStrings()) // "田中太郎"
この combineStrings メソッドは、コンパイル時には以下のように変換されています。
func combineStrings() {
let a = "田中"
let b = "太郎"
return StringBuilder.bildBlock(a,b)
}
ただ、結構特殊な機能なので、Function Builder を独自に定義する機会はあまりないと思われます。
当社で開発した予定とタスクを同時に管理できるiOSアプリ「My Schedule - マイスケジュール -」をリリースしました。ダウンロード&評価のほどよろしくお願いいたします。