LoginSignup
2
3

BuildできるのにPreviewがクラッシュする

Last updated at Posted at 2024-05-19

環境

  • Xcode 15.2
  • Swift 5.9

条件

以下4要素

  • 親View
  • 子View
  • ObservableObjectなprotocol
  • protocolに準拠したclass

が次のような構成になっているとき

  • 親ViewObservableObjectに準拠するprotocolのGeneric型を受け取る
  • 親ViewGenericなViewを受け取る子Viewを内部で宣言する
  • 親Viewは自身のbodyを構成するのに子Viewを使う

(ややこしい)

失敗例

Preview失敗.swift
protocol Presentation: ObservableObject {
  var text: String { get }
}

class MockPresenter: Presentation {
  var text = "aaa"
}

struct TestView<P: Presentation>: View {
  @StateObject var presenter: P
  var body: some View {
    WrapView {
      Text(presenter.text)
    }
  }

  struct WrapView<C: View>: View { // `GenericなViewを受け取る子View`が親Viewにネストされている
    let content: () -> C
    init(content: @escaping () -> C) {
      self.content = content
    }
    var body: some View {
      content()
    }
  }
}

#Preview {
  TestView(presenter: MockPresenter())
}
ファイル名

成功例

Preview成功.swift
protocol Presentation: ObservableObject {
  var text: String { get }
}

class MockPresenter: Presentation {
  var text = "aaa"
}

struct TestView<P: Presentation>: View {
  @StateObject var presenter: P
  var body: some View {
    WrapView {
      Text(presenter.text)
    }
  }
}

struct WrapView<C: View>: View { // 子Viewの宣言を親Viewの外に移動しただけ
  let content: () -> C
  init(content: @escaping () -> C) {
    self.content = content
  }
  var body: some View {
    content()
  }
}

#Preview {
  TestView(presenter: MockPresenter())
}
ファイル名

原因

調べたけれどよくわかりませんでした。いかがでしたか?
(ありがちなキュレーションサイトみたいになってしまいすみません。とりあえず似た状態になった人の役にたてるかも、と問題の共有のために本記事を公開します。)
PreviewのためのBuildは通常のBuildとは異なる過程を経るとどこかで聞いた気もするので関係がありそうです。

なお、SlackOverflowやApple Developper Forumにも同様な例は見当たりませんでした。
PreviewのクラッシュはenvironmentObjectCoreData, SwiftDataあたりではよく発生するようですね。
何かご存知の方がいらっしゃいましたらぜひご共有いただきたいです。

追記

またGenericsが関わっていそうなBuildはできてもPreviewできないパターンを見つけました。
ログを見ると
error: cannot find 'WrapView' in scope
とのエラーが出ていました。

これに関しては次の記事が同様の指摘をしています。

「SwiftにNameSpaceはないんだ!(だからViewをネストするな)」という意見と、「buildが通って実際に動くのにpreviewだけ動かないなら、それはpreviewのバグだ」という意見で対立しているようです。

import SwiftUI

enum NameSpace {
    struct TestView: View {
        var body: some View {
            WrapView {
                Text("a")
            }
        }
    }

    struct WrapView<C: View>: View {
        let content: () -> C
        init(content: @escaping () -> C) {
            self.content = content
        }
        var body: some View {
            content()
        }
    }
}

#Preview {
    NameSpace.TestView()
}

次のようにView protocol以外のprotocolでも同様にpreviewできません。

    struct WrapView<T: Equatable>: View {
        let a: T
        let b: T
        var body: some View {
            Text("\({a == b}().description)")
        }
    }

以下ならPreviewできる。

    struct TestView: View {
        var body: some View {
-           WrapView {
+           NameSpace.WrapView {
                Text("a")
            }
        }
    }

あるいはWrapViewTestViewにネストしてもPreviewできます。


子ViewがGenericな型を受け取らない場合は、親viewで呼び出すときにNameSpace.と指定しなくても問題なくpreviewできます。

import SwiftUI

enum NameSpace {
    struct TestView: View {
        var body: some View {
            WrapView() // NameSpace.WrapView()としなくてもpreviewできる
        }
    }

    struct WrapView: View {
        var body: some View {
            Text("a")
        }
    }
}

#Preview {
    NameSpace.TestView()
}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3