SwiftUI 崩溃:“前提条件失败:属性未能设置初始值:71”
Posted
技术标签:
【中文标题】SwiftUI 崩溃:“前提条件失败:属性未能设置初始值:71”【英文标题】:SwiftUI crash: "precondition failure: attribute failed to set an initial value: 71” 【发布时间】:2020-02-02 17:54:51 【问题描述】:我有一个非常有趣的崩溃,它只发生在非常特定的情况下。我已经向 Apple 提交了错误报告,但也许这里有人看到过类似的崩溃,知道发生了什么,并且知道解决方法?
可以在https://github.com/kevinrenskers/SwiftUICrash 找到显示崩溃的最小项目,但我还在下面添加了相关代码。该项目有 3 个视图:RootView
、DetailsView
和 ListView
。 RootView
嵌入 DetailsView
或 ListView
。
当您按下DetailsView
中的尾随导航栏按钮切换回ListView
时,会发生崩溃。应用程序崩溃并出现错误“前提条件失败:属性未能设置初始值:71”。
但是,当您使用屏幕中间的Button
切换回ListView
时,不会发生崩溃。当您从背景图像中删除 .resizable()
修饰符时,也不会发生崩溃。
此外,如果您将Group
更改为NavigationView
内的RootView
,应用程序不会崩溃。遗憾的是,这对于我的实际应用程序来说不是一个选项。
import SwiftUI
final class AppStore: ObservableObject
@Published var showingDetails = true
struct RootView: View
@EnvironmentObject private var store: AppStore
var body: some View
Group
if store.showingDetails
DetailsView()
else
ListView()
struct DetailsView: View
@EnvironmentObject private var store: AppStore
var body: some View
NavigationView
ZStack
GeometryReader geo in
Image("bg")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.frame(width: geo.size.width, height: geo.size.height)
Button("List")
self.store.showingDetails = false // <- this works fine
.padding(20)
.background(Color.white)
.navigationBarTitle(Text("Details"))
.navigationBarItems(trailing: trailingNavigationBarItem)
private var trailingNavigationBarItem: some View
Button("List")
self.store.showingDetails = false // <- this crashes the app!
struct ListView: View
@EnvironmentObject private var store: AppStore
var body: some View
NavigationView
Button("Load details")
self.store.showingDetails = true
.padding(20)
.background(Color.white)
.navigationBarTitle("List")
【问题讨论】:
我有一个很有前途的解决方法,通过 UIViewRepresentable 使用自定义 UIImageView,从而绕过可调整大小的图像:github.com/kevinrenskers/SwiftUICrash/tree/workarounds/…。它解决了崩溃,但我还不能让它全屏显示。 【参考方案1】:尝试将RootView
中的组替换为@ViewBuilder
注释:
struct RootView: View
@EnvironmentObject private var store: AppStore
@ViewBuilder
var body: some View
if store.showingDetails
DetailsView()
else
ListView()
我不确定这通常有多可靠。我过去插入@ViewBuilder
注释的成功率参差不齐,但这似乎解决了嵌套NavigationView
的问题。
【讨论】:
这是一个绝妙的修复,谢谢!遗憾的是,我有点依赖 Group,因为我有.onAppear
和 .sheet
修饰符,所以我将继续通过 UIViewRepresentable 使用我的自定义 UIImageView。但再次感谢,这绝对是我将添加到我的武器库中的工具。我也会给你积分。
实际上,我可以将所有逻辑放在返回some View
并用@ViewBuilder
注释的计算属性中,然后将修饰符附加到该属性上。一切都好!【参考方案2】:
这是替代解决方法(实际上它只是避免出现此问题的可能性),并且经过测试没有不良副作用。仅供参考...
我们的想法不是删除DetailsView
,而是使其显式处于非活动状态并隐藏。使用 Xcode 11.2 / ios 13.2 测试,没有崩溃。
struct RootView: View
@EnvironmentObject private var store: AppStore
var body: some View
ZStack
ListView()
.zIndex(store.showingDetails ? 0 : 1) // << bring to front
DetailsView()
.opacity(store.showingDetails ? 1 : 0) // << hide
.disabled(!store.showingDetails) // << deactivate
其他视图没有变化。
【讨论】:
感谢您的建议!但是,这对我来说真的不起作用,因为只有在有模型要显示时才能显示 DetailsView。如果没有模型,则显示列表。我必须让 DetailsView 下面的整个视图层次结构与一个可选模型一起工作,这真的很糟糕:) @KevinRenskers,没有模型有问题,但这当然取决于你。至少我自己找到了解决方案。 是的,我不想在问题中添加更多细节和复杂性。我最终确实解决了这个问题:我昨天晚上非常接近,今天早上终于解决了。请看下面我的回答。但再次感谢您的想法!【参考方案3】:编辑:此解决方法会导致 iPad 的拆分导航视图出现问题。请参阅我的其他答案以获得更好的解决方法。
一种解决方法是将RootView
的Group
包装在NavigationView
中,并带有一个隐藏的导航栏(每个嵌套视图都可能有自己的导航栏,并不是所有的都有):
struct RootView: View
@EnvironmentObject private var store: AppStore
var body: some View
NavigationView
Group
if store.showingDetails != nil
DetailsView(bg: store.showingDetails!)
else
ListView()
.navigationBarHidden(true)
.navigationBarTitle("")
虽然崩溃仍然非常非常奇怪。
【讨论】:
遗憾的是,一旦我开始在 iPad 上测试应用程序,这种解决方法就不再是一个好的解决方法。*** NavigationView 确实与您在 iPad 上想要的拆分视图导航样式相混淆。使用 TabView 可以解决崩溃,但是由于您无法隐藏标签栏,因此对我来说不是有效的解决方法。另一个有趣的事实:如果您首先旋转 iPad,这也可以防止崩溃的发生。这太他妈奇怪了。 如果您想尝试使用此解决方法,尤其是在 iPad 上,请查看 github.com/kevinrenskers/SwiftUICrash/tree/workarounds/…。【参考方案4】:最终的解决方法是通过 UIViewRepresentable 使用自定义 UIImageView。
struct CustomImage: UIViewRepresentable
var image: UIImage
var frame: CGRect
func makeUIView(context: Context) -> UIView
let imageView = UIImageView(frame: frame)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = image
let view = UIView(frame: frame)
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
return view
func updateUIView(_ uiView: UIView, context: Context)
然后这样使用:
GeometryReader geo in
CustomImage(image: UIImage(named: "bg")!, frame: CGRect(x: 0, y: 0, width: geo.size.width, height: geo.size.height))
.edgesIgnoringSafeArea(.all)
另见https://github.com/kevinrenskers/SwiftUICrash/tree/workarounds/CustomImage。
【讨论】:
根据我的调查,崩溃不是源自Image
本身,而是使NavigationView
内部视图全屏,因此如果您将Image
替换为(或改用),例如, Rectangle
你会遇到同样的崩溃。请注意。
最奇怪的是,如果您按下屏幕中间的按钮,崩溃不会发生。仅当您从导航栏按钮进行切换时才会崩溃。
.frame on CustomImage 是多余的,删除它。以上是关于SwiftUI 崩溃:“前提条件失败:属性未能设置初始值:71”的主要内容,如果未能解决你的问题,请参考以下文章