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 个视图:RootViewDetailsViewListViewRootView 嵌入 DetailsViewListView

当您按下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 的拆分导航视图出现问题。请参阅我的其他答案以获得更好的解决方法。


一种解决方法是将RootViewGroup 包装在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”的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI Preview 因 *** 崩溃

扩展视图时 SwiftUI 预览崩溃

Canvas SwiftUI 崩溃

滚动列表时 SwiftUI 崩溃

SwiftUI 匹配几何 + LazyVStack = 崩溃

为啥@FocusState 会导致 SwiftUI 预览崩溃