johnpatrickmorgan / NavigationBackport

Backported SwiftUI navigation APIs introduced in WWDC22
MIT License
865 stars 53 forks source link

iOS 16.1.x crash #29

Closed dhoskins closed 1 year ago

dhoskins commented 1 year ago

I am getting crashes on devices in my app (still in development) that are difficult to reproduce and debug. It seems to happen when changing the root view (from a login screen to the main app tabview).

They have been seen so far on iOS 16.1 and iOS 16.1.1. The logs in Crashlytics say:

SwiftUI/EnvironmentObject.swift:70: Fatal error: No ObservableObject of type DestinationBuilderHolder found. A View.environmentObject(_:) for DestinationBuilderHolder may be missing as an ancestor of this view.

and the stack trace mentions:

0  libswiftCore.dylib             0x37d7c _assertionFailure(_:_:file:line:flags:) + 312
1  SwiftUI                        0x15b9ea8 OUTLINED_FUNCTION_3 + 540
2  MyAppName                   0x220988 DestinationBuilderView.body.getter + 4298262920 (<compiler-generated>:4298262920)

Searching for OUTLINED_FUNCTION links me to this issue on Apple Developer which suggests the fix of switching to NavigationStack...

johnpatrickmorgan commented 1 year ago

Thanks for sharing @dhoskins. It's hard to know why the environment object is missing without a reproduction, but I'll try to reproduce it myself. Please let me know if you gain any more insight. In the meantime, a workaround might be use different NBNavigationStacks for the login flow and for the main app tabview, and transition between them, rather than sharing the same NBNavigationStack.

ezachari commented 1 year ago

same erorr, reproduce it when add UIHostingController with NBNavigationStack in UINavigationController in this case app crash when you try use popToRoot or popTo

johnpatrickmorgan commented 1 year ago

@dhoskins Are you able to provide some example code of how you are switching out the root view please? I'm interested to see where you're adding the nbNavigationDestination view modifier. Thanks!

ejubber commented 1 year ago

Hey there. I'm getting the same error in my own app. I have a UIViewController that embeds a UIHostingController, containing a SwiftUI View using a NBNavigationStack. I'd be happy to provide some example code if that would be helpful in debugging.

johnpatrickmorgan commented 1 year ago

Thanks @ezachari and @ejubber for the steer towards a UIHostingController setup. I'll try to reproduce with that...

johnpatrickmorgan commented 1 year ago

I haven't managed to reproduce this in a simple setup so far. @ejubber if you have some example code, that would be very helpful please. Here's what I tried but it works as intended:

import NavigationBackport
import SwiftUI
import UIKit

struct RehostingView: UIViewControllerRepresentable {
  typealias UIViewControllerType = UINavigationController

  func makeUIViewController(context: Context) -> UINavigationController {
    let vc = UINavigationController()
    vc.viewControllers = [HostingViewController()]
    return vc
  }

  func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}

class HostingViewController: UIHostingController<HostedView> {
  init() {
    super.init(rootView: HostedView())
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder, rootView: HostedView())
  }
}

struct HostedView: View {
  @State var path = NBNavigationPath()
  @State var showLocal = false

  var body: some View {
    NBNavigationStack(path: $path) {
      VStack {
        Toggle("Show local", isOn: $showLocal)
        NBNavigationLink(value: 42, label: { Text("Show 42") })
      }
      .nbNavigationDestination(for: Int.self, destination: { number in
        Text("\(number)")
      })
      .nbNavigationDestination(isPresented: $showLocal, destination: { Text("Local") })
    }
  }
}
ejubber commented 1 year ago

Hi @johnpatrickmorgan. I apologize, I believe the issue I was having was a problem with my own code, in which I was not correctly adding the HostingController as a child of the parent UIViewController. I'll check again this week to confirm that it was my own issue and update here. Thanks for looking into it.

BTW - out of the box, I'm unable to build on simulator because of this issue: Could not find module 'navigation backport' for target 'x86_64-apple-ios-simulator'; found: arm64-apple-ios-simulator. I've tried a number of solutions around online about modifying build settings, but to no avail. Have you been able to build on simulator on an M1 mac?

johnpatrickmorgan commented 1 year ago

@ejubber Thanks for letting me know :)

Re the build issue - is this affecting you building the example app in the repo, or your own app? I don't have an M1 Mac to test on, so I'm not able to reproduce that.

ejubber commented 1 year ago

Looks like it's just an issue with my own app - I just ran the example app and it works as expected. Got some debugging to do. Thanks!

johnpatrickmorgan commented 1 year ago

Closing as I can't reproduce this, even when I add a UIKit layer in between the NBNavigationStack and the `NBNavigationLink.

Example code here ```swift import NavigationBackport import SwiftUI import UIKit struct ParentView: View { @State var path = NBNavigationPath() var body: some View { NBNavigationStack(path: $path) { HostingContainerVC() } } } struct HostingContainerVC: UIViewControllerRepresentable { typealias UIViewControllerType = UIViewController let hostingVc = HostingViewController() func makeUIViewController(context: Context) -> UIViewController { let vc = UIViewController() hostingVc.view.translatesAutoresizingMaskIntoConstraints = false // vc.addChild(hostingVc) // works even if this line is omitted vc.view.addSubview(hostingVc.view) // hostingVc.didMove(toParent: vc) // works even if this line is omitted NSLayoutConstraint.activate([ hostingVc.view.widthAnchor.constraint(equalTo: vc.view.widthAnchor, multiplier: 1), hostingVc.view.heightAnchor.constraint(equalTo: vc.view.heightAnchor, multiplier: 1), hostingVc.view.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor), hostingVc.view.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor) ]) return vc } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } class HostingViewController: UIHostingController { init() { super.init(rootView: HostedView()) } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder, rootView: HostedView()) } } struct HostedView: View { @State var showLocal = false var body: some View { VStack { Toggle("Show local", isOn: $showLocal) NBNavigationLink(value: 42, label: { Text("Show 42") }) } .nbNavigationDestination(for: Int.self, destination: { number in Text("\(number)") }) .nbNavigationDestination(isPresented: $showLocal, destination: { Text("Local") }) } } ```