SwiftUIはUIKitに比べて比較的簡単なコードで記述が可能です。
チュートリアルを進めるだけで、おおよそコードの書き方は理解できるのですが、いざリファレンスマニュアルを読んでいくと、予想外の構文が並んでいて戸惑ったので、改めて解読してみました。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.1
【macOS】Catalina バージョン 10.15
テキスト(Text)
UIKitではUILabel部品に該当するViewです。
使い方は簡単で、
1 2 3 |
Text("カピ通信") |
と、表示する文字列を引数で指定するだけです。
リファレンスマニュアルで、Text構造体のイニシャライザ(初期化子)を見ると次のようなっています。
1 2 3 |
init<S>(_ content: S) where S : StringProtocol |
まずはこれを読み解いてみます。
引数contentにはviewに表示する文字列を指定します。
"<S>"は引数contentの型であるSがジェネリクス(汎用)型である事を示します。
また"where S: StringProtocol"によって、型SがStringProtocol に準拠している事を表しています。
標準ライブラリでStringProtocolに準拠しているのはString型かSubString型です。
contentの引数ラベルが_(アンダースコア)で省略形である事を示しているので、引数名が不要となります。
結果的に引数にはString型の文字列"カピ通信"を渡すだけで想定通りの動作になるわけです。
ボタン(Button)
UIKitではUIButtonに該当するView。
使い方はこんな感じ、
1 2 3 4 5 |
Button(action: {print("カピ通信")}) { Text("タイトル出力ボタン") } |
"タイトル出力ボタン"と表示されたボタンを押すと、文字列"カピ通信"がデバグエリアに出力される処理です。
この構文を、リファレンスマニュアルで見ると、次の通り。
1 2 3 4 5 |
struct Button<Label> where Label : View init(action: @escaping () -> Void, @ViewBuilder label: () -> Label) |
まず1行目です、ここでは先程出てきた、ジェネリクスが使われています。
"<Label>"で型名Labelがジェネリクス(汎用)型である事、"where Label : View"でViewプロトコルに準拠している事を示します。
続いて2行目はButton構造体のイニシャライザ(初期化子)です。
1つ目の引数actionには、ボタンをタップした時に実行するアクションを定義します。
"() -> Viod"はクロージャー(無名関数)を示す表記です。
クロージャーには{}で囲んだ処理手続きの記述が可能であり、"-> Void"なので戻り値が無い事を示しています。
その前にある"@escaping"は、init()処理が終わった後も、クロージャーへの参照が残る事を表していますが、使う上で特に意識する必要はありません。
2つ目の引数labelも先のactionと同様で、クロージャーを定義します。
但し、"-> Label"とあるので、戻り値の型はLabelです。
型名Labelは1行目で示すように、Viewプロトコルに準拠する制約がありますので、Text()やImage()などのSwiftUI部品を記述する事ができます。
以上の説明から、Button部品は次のように使う事がわかります。
1 2 3 |
Button(action: {print("カピ通信")}, label: {Text("タイトル出力ボタン")}) |
また、SwiftのクロージャーにはTrailing Closuresと呼ばれる書き方があります。
これは関数の引数の末尾(最後)がクロージャーの場合、処理部分を外に配置できる記述方法です。
先程のコードは次のように書き換えられます。
1 2 3 4 5 |
Button(action: {print("カピ通信")}) { Text("タイトル出力ボタン") } |
この方がすっきりして見やすいですね。
最後に"@ViewBuilder"について。
"@ViewBuilder"はカスタム属性定義で、後ろの引数labelの戻り値を複数設定する事が可能である事を示します。(最大10個まで)
ようするに次のような記述が可能です。
1 2 3 4 5 6 7 |
Button(action: {print("カピ通信")}) { Image(systemName: "hand.point.right") Text("タイトル出力ボタン") Image(systemName: "hand.point.left") } |
出力イメージはこちら
もう一つのイニシャライザ
Button部品にはもう一つ別のイニシャライザがあるので、そちらも読み解いてみます。
1 2 3 |
init<S>(_ title: S, action: @escaping () -> Void) where S : StringProtocol |
このイニシャライズには"Available when Label is Text."(ラベルがテキストの場合に使用可能)の但し書きがあります。
最初の引数titleがラベルに相当します。
titleの型名に相当するSは、その前の"<S>"でジェネリクス(汎用)型とわかります。
さらに後ろの"where S; StringProtocol"によって、String型あるいはSubString型に準拠する制約がつきます。
"action: @escaping () -> Void"は先程と同様で、ボタンをタップした時に実行するアクションをクロージャー(無名関数)で定義します。
関数末尾のクロージャーである為、処理部分を外に配置可能です。
総合すると次のような記述になります。
1 2 3 4 5 |
Button("タイトル出力ボタン") { print("カピ通信") } |
ラベルがテキストのみの単純なボタンであれば、こちらの記述の方がシンプルです。
Viewのレイアウト(VStack)
VStackはViewを縦方向に並べる役割を持つ部品です。
リファレンスマニュアル上の構文は次の通り。
1 2 3 4 5 |
@frozen struct VStack<Content> where Content : View init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) |
1行目では型名ContentがViewプロトコルに準拠している事を示しています。
@frozenは意味がよくわかりませんでしたが、たぶん気にしなくてもよさそうです。
2行目はVStack構造体のイニシャライザ(初期化子)です。
1つ目の引数alignmentには部品の配置方式をHorizontalAlignment型で指定します。
"= .center"はalignmentの初期値に.centerが設定されている事を表しています。
ちなみに、Structで引数の初期値が設定されている場合、引数の指定を省略する事が可能です。
続いて、2つ目の引数spacingには部品間の距離を指定します。
こちらも"= nil"がと初期値が設定されていますので、引数を省略する事が可能です。
最後の引数contentはViewプロトコルに準拠しているContent型を戻り値とするクロージャー(無名関数)、かつ関数の末尾ですので、処理部分を外に配置可能です。
"@ViewBuilder"はButtonの説明でも出てきたカスタム属性定義で、contentの戻り値を複数設定する事が可能である事を示すものでした。
これらを総合すると、次のような記述が可能になるわけです。
1 2 3 4 5 6 7 |
VStack { Text("カピ通信") Text("趣味プログラマーの忘備録") Text("https://capibara1969.com/") } |
出力イメージはこちら
最後に
ほんのさわり程度ですが、SwiftUIのリファレンスを読み解いてみた記録です。
SwiftUI理解を進める足がかりになるかな?