ボクココ

サービス開発を成功させるまでの歩み

SwiftUI で print が動作しない理由

ども、@kimihom です。

f:id:cevid_cpp:20210223221609j:plain

SwiftUI で開発してて、最も大きな謎であった print の出力が動かない理由がようやく分かったのでまとめておく。

SwiftUI の基本記法

単にテキスト表示させる SwiftUI は

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}

となる。さて、ここで変数を持って print で検証したいってケースは当然のように出てくるだろう。

import SwiftUI

struct ContentView: View {
    var date = "2021/2/23"
    var body: some View {
        print(date)
        Text("Hello World \(date)")
    }
}

これだけで、 body の { の部分でエラーが発生する。

Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols

何が原因なのか、全くわからない。しかもエラー記述場所は { の部分から出てくる。この体験は SwiftUI プログラマーにとって大きな課題の一つになるであろう。

View 内は、そもそも Swift 文を書くところではない

SwiftUI の View 内では、 if文 も書けるし、ForEach で繰り返し処理も書けるから、Swift 文を同じように書ける、と考えがちだろう。しかし、それは No である。

SwiftUI では ファンクションビルダと呼ばれる Swift 新構文によって、1行ずつ書いたコードが、自動で組み合わされる @_functionBuilder という機能がある。

この ファンクションビルダを使うと、、衝撃のコードが実現できるようになる。

// @_functionBuilder で 文字を組み合わせる 自前 `StringBuilder の ab()` を定義

// `ab()` の実行 !!!
@StringBuilder func ab() -> String {
  "こんにちは"
  "テスト"
}
print(ab()) // こんにちはテスト

// 以下と同じ
func ab() -> String {
  let a = "こんにちは"
  let b = "テスト"
  return StringBuilder.buildBlock(a, b)
}
print(ab()) // こんにちはテスト

今回の場合、String を組み足しただけのコードとなるが、なんと "こんにちは" "テスト" を文字列として各行に書くだけで、文字が足される処理を実装できる。ここは、Swift 構文ではなく、ファンクションビルダで定義した構文を書くことになるわけである。

そもそも SwiftUI ではなぜ for文ではなく、ForEachで書かなければならないのかもこれで理解できた。ForEach は ファンクションビルダないで使える特殊な書き方であり、通常の for文では ファンクションビルダとして書けないという理由なのだろう。

ただ if文 は普通の Swift コードを書いてるのと同じように書けてしまうので、最初は この var body: some View 内は普通の Swift 文をかけると誰もが思ってしまうことだろう。

終わりに

Swift を勉強してて もっと大きな謎だった "print 文が SwiftUI 内で書けない" 問題を理解することができた。 この問題の理由は、以下の本だけで知ることができた。

SwiftUI の本を4,5 冊くらい読んだけど, some の定義は一旦なんなんだ? なんで print が SwiftUI 内で書けないんだ? といった SwiftUI を最初に触る人が必ず思う疑問を、徹底して解説してくれている唯一の本だった。レベルが1上がった。

SwiftUI 徹底入門

SwiftUI 徹底入門