SwiftUIはこれまでのUIKitを用いたUIの作成に変わる新たな方法としてWWDC 2019で発表されたフレームワークです。最近ではWidgetがSwiftUIでないと作れなかったりと、将来的にはSwiftUIを用いた開発が主流になっていくと思います。
自分もだいぶ前からSwiftUIの勉強をなかなか気乗りせずにいましたが、Widgetを期に本格的に勉強を始めました。。
↓有限不実行のツイート
そろそろSwiftUIマジで本格的に勉強しよう
— M (@p_x9) July 8, 2020
自分がSwiftUIを敬遠してしまっていたのは、コードを見て「SwiftUIはSwiftじゃない別の言語にしか見えない」と思ってしまったのが理由です。しかし、よく調べたり内部実装を紐解いていくうちに、「SwiftUIはやはりSwiftだ」と理解することができました。
そこで今回はSwiftUIの記法に違和感を覚えたが、それをSwiftらしく理解できた点についていくつか書いていきます。
1,returnの省略
まず以下のコードを見てください。
private struct ContentView: View {
var body: some View{
Text("test")
}
}
これにはSwift5.1から新たに使用可能となった記法が使われています。これまでの記法で表せば、上記のコードは以下のように書けます。
private struct ContentView: View {
var body: some View{
return Text("test")
}
}
つまり、getterを記述する際に単一式を返す場合、returnを省略できるということです。前述の通りこれはSwift5.1からの機能であるので、SwiftUIに限らずこれからは通常のSwiftでも使用できる記法です。
2,プロパティの型指定をプロトコルで指定できる。
SwiftUIにおける”View”はUIKitのUIViewとは違い、classやstructではなくプロトコルです。
これまでプロパティの型を「〇〇プロトコルに適合した何かしらの型」というように柔軟に指定することはできませんでしたが、これもSwift5.1から新しくできるようになりました。型にプロトコルを指定する際、プロトコル名の前にsomeを記述します。上記のコードではbodyプロパティがその例です。
以上を踏まえれば以下のような意味のないコードも書けるということです。
var test: some Equatable = 1
3,PropertyWrapper
SwiftUIを触っていると、プロパティの宣言varの前に@State
やら@Binding
,@Environment
などというものが出てくると思います。これもまたSwift5.1からのプロパティラッパーという機能です。これはgetterとsetterにある共通の処理がある場合、プロパティラッパーを定義してそのgetterやsetterの共通の処理を適宜するという機能です。
例えばUserDefaultsに保存されている値を扱う際、値を取得(get)する際にはUserdefaultから読み出す処理、値をセットする際にはUserDefaultsに保存する処理を書く必要があります。UserDefaultsを保存された値を複数扱う場合、その取得・保存の処理をそれぞれ逐一記述するのは面倒です。こういった場合にPropertyWrapperが有用なわけです。(このコードを参照)
4,関数の引数のうち一番最後のクロージャは外に出せる
HStack,VStackやForEachなどはこの、クロージャは外に出せることを利用して記述しています。
例えば以下のコードがあったとします。
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: {_ in
print("cancel")
})
この時,引数のクロージャは、外に出して以下のように記述することができます。
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel){_ in
print("cancel")
}
さらに引数がクロージャのみの場合は以下の様に()も省略できます。
//通常の書き方
var array = [Double](stride(from: 0, to: 1, by: 0.1))
array = array.map({
sin($0)
})
//クロージャを外に出した書き方
var array = [Double](stride(from: 0, to: 1, by: 0.1))
array = array.map{
sin($0)
}
加えて以下のようにクロージャの引数が複数ある場合はどうでしょう。
UIView.animate(withDuration: 6, animations: {
self.view.backgroundColor = .cyan
}, completion: {
print("completed:\($0)")
})
クロージャが複数ある場合は最後のクロージャのみ外に出すことができます。
UIView.animate(withDuration: 6, animations: {
self.view.backgroundColor = .cyan
}){
print("completed:\($0)")
}
5,Function Builder
HStackやVStackのこの構文にはかなり違和感を覚えると思います。クロージャ内に要素を羅列するだけでそれらがよしなに扱われてしまうのには自分もかなり違和感を覚えました。
VStack{
Text("1")
Text("2")
Text("3")
}
こちらもまたSwift5.1からの新機能ファンクションビルダを使用して実装されているそうです。ファンクションビルダを使用すれば、引数を羅列するだけで任意のインスタンスを作成できます。
例えば以下の構造体があったとします。
struct Person{
let id:Int
let name:String
}
この時ファンクションビルダを作成します。ビルダの作成は@_functionBuilder
属性を指定した構造体を作成し、buildBlock関数を定義することで作成できます。
@_functionBuilder struct PersonBuilder{
public static func buildBlock(_ id:Int,_ name:String)->Person{
.init(id: id, name: name)
}
}
このビルダをしようすると以下のような引数を羅列するだけでインスタンスを生成できる関数が作成できます。
@PersonBuilder func test()->Person{
1
"Linus Benedict Torvalds"
}
これは関数やイニシャライザの引数にクロージャとして使用することもできます。実際にHStackやVStackなどのイニシャライザの引数には@ViewBuilder
のクロージャが存在しており、これを利用して前述のような初見では違和感のある記法が出来るようにされています。
func test(@PersonBuilder closure:()->Person)->Person{
return closure()
}
最後に
これからも何か気づきがあれば更新していきます。ご拝読ありがとうございました。
参考
https://github.com/batuhansk/UserDefault
https://qiita.com/yimajo/items/32a5209bd73580d283a1
https://www.vadimbulavin.com/swift-function-builders-swiftui-view-builder/