【问题标题】:SwiftUI crash: "precondition failure: attribute failed to set an initial value: 71”SwiftUI 崩溃:“前提条件失败:属性未能设置初始值: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")
    }
  }
}

【问题讨论】:

标签: swiftui


【解决方案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 是多余的,删除它。
猜你喜欢
  • 1970-01-01
  • 2020-02-22
  • 2016-07-17
  • 2016-04-26
  • 1970-01-01
  • 1970-01-01
  • 2019-11-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多