🗂

if 式と switch 式による SwiftUI のプレビューエラー対策

2023/12/06に公開

謝辞

2023年11月14日に開催された勉強会「potatotips #85 iOS/Android開発Tips共有会」での発表内容を基にした記事です。

また、この記事はSwiftWednesday Advent Calendar 2023の6日目の記事です。
昨日は @uhooi さんでした。

Swift 5.9の新機能: 式としてのifswitch

Swift 5.9からifswitchを式として表現できるようになりました。これはSE-0380により実現されました。
これによりifswitchをよりシンプルに書くことが可能になりました。以下に具体的な例を挙げます。

enum内のswitch表現の簡略化

従来のSwiftでは、enumの各ケースに対する値をswitch文を用いて設定していました。以下はその例です。

enum SomeEnum {
  case moon
  case star
  
  var someValue: String {
    switch self {
    case .moon: return "moon 🌕"
    case .star: return "star ⭐️"
    }
  }
}

Swift 5.9では、このコードを以下のようにreturnを省略しシンプルに書くことができます。

enum SomeEnum {
  case moon
  case star
  
  var someValue: String {
    switch self {
    case .moon: "moon 🌕"
    case .star: "star ⭐️"
    }
  }
}

メソッド内でのswitch表現の改善

メソッド内で特定の値をenumの型によって設定する場合、以前は次のように記述していました。

func someFunction(type: SomeEnum) {
  let value: Int
  switch type {
  case .moon: value = 100
  case .star: value = 999
  }
  
  // valueを使った処理
}

Swift 5.9では、以下のように直感的にコードを書くことが可能になります。

func someFunction(type: SomeEnum) {
  let value = switch type {
  case .moon: 100
  case .star: 999
  }
  
  // valueを使った処理
}

SwiftUIプレビュー時の問題と解決策

SwiftUIのプレビュー時にifswitchの式を利用すると、特定の状況でコンパイルエラーが発生します。例えば、以下のようなBool値に基づいてTextを返すViewがあります。

struct ContentView: View {
  let isSwift: Bool
  
  var body: some View {
    makeLanguageView()
  }
  
  func makeLanguageView() -> some View {
    if isSwift {
      Text("Swiftだよ")
    } else {
      Text("Kotlinだよ")
    }
  }
}

この場合、以下のようなエラーメッセージが表示されることがあります。

'if' may only be userd as expression in return, throw, or as the source of an assignment

通常のビルドであれば問題なく成功しますが、Preview時のビルドではコンパイルエラーが発生します。これはPreviewProviderを利用した場合でも同様です。
このエラーはXcode起因の問題であり、将来的には修正されることが期待されます。

解決方法

以下は、この問題に対処するための3つの方法です。

@ViewBuilderキーワードの使用

@ViewBuilderキーワードをメソッドに付与することで、Preview時のエラーを回避できます。

struct ContentView: View {
  let isSwift: Bool
  
  var body: some View {
    makeLanguageView()
  }
  
  @ViewBuilder
  func makeLanguageView() -> some View {
    if isSwift {
      Text("Swiftだよ")
    } else {
      Text("Kotlinだよ")
    }
  }
}

明確な戻り値の指定

また、以下のようにメソッドの戻り値をOpaque Typeであるsome ViewからTextに変えることでもエラーを回避することができます。

struct ContentView: View {
  let isSwift: Bool
  
  var body: some View {
    makeLanguageView()
  }
  
  func makeLanguageView() -> Text {
    if isSwift {
      Text("Swiftだよ")
    } else {
      Text("Kotlinだよ")
    }
  }
}

Existential Typeへの置き換え

メソッドの戻り値をsome Viewからany Viewに変える方法です。ただし、この方法ではメソッドの呼び出し元での型キャストが必要になるため、上記の2つの方法が推奨されます。

struct ContentView: View {
  let isSwift: Bool
  
  var body: some View {
    makeLanguageView() as! View
  }
  
  func makeLanguageView() -> any View {
    if isSwift {
      Text("Swiftだよ")
    } else {
      Text("Kotlinだよ")
    }
  }
}

まとめ

この記事では、Swift 5.9のアップデートの1つであるifswitchの式表現について解説しました。また、ifswitchの式をSwiftUIプレビュー時に使用する際の課題点と、それを解決する方法について説明しました。

明日は @startaiyo さんです!

開発環境

  • Swift compiler version info:
    • Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
  • Xcode version info:
    • Xcode 15.0 Build version 15A240d

参考引用文献

DeNA Engineers

Discussion