環境
- Xcode 15.2
- Swift 5.9
条件
以下4要素
親View
子View
ObservableObjectなprotocol
protocolに準拠したclass
が次のような構成になっているとき
-
親View
はObservableObjectに準拠するprotocolのGeneric型
を受け取る -
親View
はGenericなViewを受け取る子View
を内部で宣言する - 親Viewは自身のbodyを構成するのに子Viewを使う
(ややこしい)
失敗例
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())
}

成功例
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のクラッシュはenvironmentObject
やCoreData
, 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")
}
}
}
あるいはWrapView
をTestView
にネストしても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()
}