skip to Main Content

I had the following code in Xcode 12.4 that worked perfectly

ScrollView(.horizontal, showsIndicators: false) {
        
    LazyHGrid(rows: rows, spacing: 0) {
            
        HStack {
                        
            if (type == "Quiz") {
                        
                NavigationLink(destination: Quiz(id: quiz.id)) {
                                
                    VStack(alignment: .leading) {
                                    
                        Text("Quiz")
                            .font(.headline)
                            .foregroundColor(.white)
                            .padding(.top, 8)
                            .padding(.leading)
     
                    }
                    .background(Color.green)
                    .cornerRadius(12)
                    .shadow(color: .green, radius: 3, x: 0.0, y: 0.0)
                            
                }
                            
            } else {
                            
                NavigationLink(destination: Survey(id: survey.id)) {
                                
                    VStack(alignment: .leading) {
                                    
                        Text("Survey")
                            .font(.headline)
                            .foregroundColor(.white)
                            .padding(.top, 8)
                            .padding(.leading)
                                    
                    }
                    .background(Color.green)
                    .cornerRadius(12)
                    .shadow(color: .green, radius: 3, x: 0.0, y: 0.0)
                            
                }
                            
           } // End If
                    
           if (type == "Quiz") {
                        
               NavigationLink(destination: QuizResults(id: quiz.id)) {
                                
                   VStack(alignment: .leading) {
                            
                       Text("Quiz Results")
                           .font(.headline)
                           .foregroundColor(.white)
                           .padding(.top, 8)
                           .padding(.leading) 
                        
                   }
                   .background(Color.blue)
                   .cornerRadius(12)
                   .shadow(color: .blue, radius: 3, x: 0.0, y: 0.0)
                                
               }
                            
           } else {
                                
               NavigationLink(destination: SurveyResults(id: survey.id)) {
                                    
                   VStack(alignment: .leading) {
                                
                       Text("Survey Results")
                           .font(.headline)
                           .foregroundColor(.white)
                           .padding(.top, 8)
                           .padding(.leading)
                            
                   }
                   .background(Color.blue)
                   .cornerRadius(12)
                   .shadow(color: .blue, radius: 3, x: 0.0, y: 0.0)
                                    
              }
                                
          } 
                    
      }
      .padding([.leading, .trailing], 25)
            
}
.frame(height: 100)

I just updated Xcode to 12.5 and the above does not work any more.

It was working fine in 12.4!?

Now when I click the ‘Quiz’ element, it starts the transition to the Quiz View which is displays it but immediately closes the view and I’m back in the Detail View!?

Can someone see what I am doing wrong, and why now based on the update to 12.5 this stopped working?

UPDATE

I refined the code to the minimal possible reproducible form. What seems to be happening is that I have two or more NavigationLinks sets.

the first is the set to navigate the user to either the Quiz or Survey which the if statement addresses the user to the correct view to fill in.

Where the issue is in 12.5 is that the second set where the user can click to go see the overall results of the Quiz or Survey does not work when it’s directly after the first navigation.

Like I said before hand it worked perfectly in 12.4 but seems like 12.5 does not agree with it. Can someone offer a better way for the user to click an element to either go fill in a quiz or survey or go see the results of a quiz or survey?

11

Answers


  1. I got exactly the same problem, everything works fine with Xcode 12.4.

    https://developer.apple.com/forums/thread/677333

    I try to following this thread, it might work but on some case, I still have this bug.

           NavigationLink(destination: EmptyView()) {
               EmptyView()
           }
    

    Apparently, you can put this 3 lines of code close to your NavigationLink…
    If someone got a better answer I will really appreciate it !

    Login or Signup to reply.
  2. What a horrible bug! From my testing and some googling it happens when there are exactly 2 navigation links in a view. The code in the question has 4 but because of the if else statements there are effectively only 2 at a time.

    I often don’t know how many nav links I will have as it depends on what data the user has added/how many search hits there are etc. To be safe I’ve made a tripleEmptyNavigationLink modifier which I’ve stuck at the end of all my views. It’s solving the popping behaviour but I’m still getting the ‘Unable to present’ warnings. Would love to know if anyone has anything better than this!

    
    import SwiftUI
    
    struct TripleEmptyNavigationLink: View {
        var body: some View {
            VStack {
            NavigationLink(destination: EmptyView()) {EmptyView()}
            NavigationLink(destination: EmptyView()) {EmptyView()}
            NavigationLink(destination: EmptyView()) {EmptyView()}
                
            }
        }
    }
    
    
    struct TripleEmptyNavigationLinkBackground: ViewModifier {
        func body(content: Content) -> some View {
            content
                .background(TripleEmptyNavigationLink())
        }
    }
    
    
    
    extension View {
        func tripleEmptyNavigationLink()-> some View {
            self.modifier(TripleEmptyNavigationLinkBackground())
        }
    }
    

    usage:

    MyView()
    .tripleEmptyNavigationLink()
    
    Login or Signup to reply.
  3. I got exactly the same problem.

    my code:

    class NavigationManager: ObservableObject {
        
        static let shared: NavigationManager = {
            return NavigationManager()
        }()
        
        @Published var showingMain: Bool
        @Published var showingSub: Bool
        @Published var content: AnyView
    
        init() {
            showingMain = false
            showingSub = false
            content = AnyView(EmptyView())
        }
        
        func forward<T:View>(content: @escaping () -> T ) {
            showView()
            self.content = AnyView(content())
        }
        
        private func showView() {
            if !showingMain,!showingSub {
                showingMain = true
            } else if showingMain,!showingSub {
                showingSub = true
            } else if !showingMain,showingSub {
                showingMain = true
            }
        }
    }
    
    struct NavigationLinkGroup: View {
        @EnvironmentObject var navigationManager: NavigationManager
        
        var body: some View {
            Group {
                NavigationLink(destination: navigationManager.content, isActive: $navigationManager.showingMain) {EmptyView()}
                
                NavigationLink(destination: navigationManager.content, isActive: $navigationManager.showingSub) {EmptyView()}
            }
        }
    }
    
    struct ContentView: View {
        var body: some View {
            NavigationView {
                NavigationLinkGroup()
            }
        }
    }
    
    

    https://github.com/Ftrybe/CustomBackButtonOfSwiftUIApp/tree/master/CustomBackButtonOfSwiftUI

    Login or Signup to reply.
  4. Like anybody else on iOS 14.5.1 my application is hit by this awful bug. I have more than 3 NavigationLinks in the page, and I was not lucky to modify the numbers of the NavigationLinks (by adding a dummy NavigationLink) to get the correct behaviour.

    A workaround that is Okay for me is to add a NavigationLink conditionally into the view.

    Instead of:

    var body: some View {
      NavigationLink(destination: AnotherView(), isActive: $someCondition) { EmptyView() }
    }
    

    I have this:

    var body: some View {
      if someCondition {
        NavigationLink(destination: AnotherView(), isActive: $someCondition) { EmptyView() }
      }
    }
    

    The behaviour is not exactly the same, as you lose some navigation animation candy, but at least you have a working application again with relatively easy to understand fix.

    You can also short-circuit it to 14.5 only, and normal behaviour elsewhere:

      /// Assumes this gets fixed by Apple until 14.6 is out
      var onIOS14_5: Bool {
        let systemVersion = UIDevice.current.systemVersion
        return systemVersion.starts(with: "14.5")
      }
    
      var body: some View {
    
      if !onIOS14_5 || someCondition {
        NavigationLink(destination: AnotherView(), isActive: $someCondition) { EmptyView() }
      }
    }
    

    Perhaps this helps someone and lets all hope Apple will fix this embarrasing bug. Now I want my half day back.

    Login or Signup to reply.
  5. Adding a NavigationLink with an empty view didn’t work for me. I solved my issue removing all NavigationLinks from the ForEach and using a single one to control the navigation to the detail view, a tap gesture and 2 state variables to keep track on what is being tapped on.

    The example broken code and fix can be found at Paul Hudson’s site.

    https://www.hackingwithswift.com/forums/swiftui/unable-to-present-please-file-a-bug/7901/8237

    Below is the complete working version

    import SwiftUI
    
    struct NavigationViewOptions {
        enum OptionType { case main, optional }
        typealias Option = (id: UUID, value: String, type: Self.OptionType)
        static var options: [Option] = [
            (UUID(), "Option 1", .main),
            (UUID(), "Option 2", .optional),
            (UUID(), "Option 3", .main),
            (UUID(), "Option 4", .main),
            (UUID(), "Option 5", .optional),
        ]
            
        static func buildView(for option: Option) -> some View {
            switch option.type {
            case .main:
                return Text("Main Option selectedn(option.value)").font(.title).fontWeight(.bold)
            case .optional:
                return Text("Optional Option selectedn(option.value)").font(.title3).italic().fontWeight(.medium)
            }
        }
    }
    
    struct NavigationViewWorking: View {
    
        // State variables to leep track of what option has been tapped on and when to navigate to new view
        @State private var selectedOption: NavigationViewOptions.Option = (id:UUID(),"",.main)
        @State private var showDetail: Bool = false
        
        var body: some View {
            NavigationView {
                ScrollView{
                    VStack (alignment:.leading) {
                        Text("NAVIGATION FIX FOR:nUnable to present. Please file a bug.")
                            .padding(.bottom, 40)
    
                        ForEach(NavigationViewOptions.options, id: .id) { option in
                            Text(option.value)
                                .font(.title)
                                .padding(.vertical, 10)
                                .foregroundColor(.accentColor) // same color as navigationLink
                                // handle tap on option
                                .onTapGesture {
                                    selectedOption = option
                                    showDetail = true
                                }
                        }
                        Spacer()
                        NavigationLink("", destination: NavigationViewOptions.buildView(for: selectedOption), isActive: $showDetail)
                            .opacity(0)
                    }
                    .navigationTitle("Options")
                }
                // INITIAL DETAIL VIEW
                Text("Select option from the left")
            }
        }
    }
    
    Login or Signup to reply.
  6. It seems that if there’s more than one NavigationLink in NavigationView, this bug will be filed.

    Here’s my solution.

    import SwiftUI
    
    enum MyLink {
        case myView1
        case myView2
    }
    
    struct MyView1: View {
        var body: some View {
            Text("MyView1")
        }
    }
    
    struct MyView2: View {
        var body: some View {
            Text("MyView2")
        }
    }
    
    struct ExampleView: View {
        @State var currentLink: MyLink = .myView1
        @State var isLinkViewShow: Bool = false
        
        func getLinkView(_ myLink: MyLink) -> some View {
            if myLink == .myView1 {
                return AnyView(MyView1())
            } else {
                return AnyView(MyView2())
            }
        }
        
        var body: some View {
            NavigationView {
                VStack {
                    NavigationLink("", 
                                   destination: getLinkView(currentLink), 
                                   isActive: $isLinkViewShow)
                    
                    // Press to navigate to MyView1
                    Button(action: {
                        currentLink = .myView1
                        isLinkViewShow = true
                    }) {
                        Text("To MyView1")
                    }
                    
                    // Press to navigate to MyView2
                    Button(action: {
                        currentLink = .myView2
                        isLinkViewShow = true
                    }) {
                        Text("To MyView2")
                    }
                }
            }
        }
    }
    
    Login or Signup to reply.
  7. In Xcode13 beta still has this issue.

    So far solution:

    1、Wrap NavigationLink with List or Form:

        List {
            NavigationLink(destination: Text("1")) {
                Text("1")
            }
            NavigationLink(destination: Text("2")) {
                Text("2")
            }
            NavigationLink(destination: Text("3")) {
                Text("3")
            }
        }
    

    2、Or Use one NavigationLink, and create destination view from func:

    struct TaskIndexPage: View {
        
        func buildView() -> some View {
            // return you destination
            switch self.option {
            case 1:
                return Text("(option)")
            default:
                return Text("(option)")
            }
        }
        
        @State private var showDetail: Bool = false
    
        @State private var option: Int = 0
    
        var body: some View {
    
            VStack {
                Button {
                    showDetail = true
                    option = 1
                } label: { Text("button 1") }
                Button {
                    showDetail = true
                    option = 2
                } label: { Text("button 2") }
                Button {
                    showDetail = true
                    option = 3
                } label: { Text("button 3") }
            }
            // handle navigating
            NavigationLink(destination: self.buildView(), isActive: $showDetail) {}.opacity(0)
            
        }
    }
    
    Login or Signup to reply.
  8. Adding a delay gets auto-navigation working again.

    NavigationLink(destination: PopupView(),isActive: $showView){}
    

    &

    .onAppear {
    if (test()){
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {showView = true}
        }
    }
    
    Login or Signup to reply.
  9. For me the correct answer didn’t work.
    It showed Unable to present-message and then required view was pushed and poped out back quickly.
    While playing around I found a working solution. I keep NotificationLink‘s without label set as a plain List items.

    NavigationView {
      ZStack {
        List {
          NavigationLink(isActive: $isFirstViewPresented,
                         destination: firstView,
                         label: EmptyView.init)
          NavigationLink(isActive: $isSecondViewPresented,
                         destination: secondView,
                         label: EmptyView.init)
        }
        .listStyle(.plain)
        //...
        Button("Show first view") { isFirstViewPresented.toggle() }
        Button("Show second view") { isSecondViewPresented.toggle() }
      }
    }
    

    Don’t forget to wrap active-properties with @State.

    It also has some benefits as for me (all the navigation links are placed at the top of the view-getter and I don’t need to look for it through all the code.

    Login or Signup to reply.
  10. I could never find a reliable solution to this horrible bug. So I decided to create a custom NavigationLink, https://gist.github.com/Arutyun2312/a0dab7eecaa84bde99c435fecae76274. This works way better than expected, because all swiftui related functions continue working as usual. Seems like the bug is specifically with NavigationLink.

    struct NavigationLink: View {
        fileprivate init<T: View>(body: T) {
            self.body = .init(body)
        }
    
        let body: AnyView
    }
    
    private struct NavigationLinkImpl<Destination: View, Label: View>: View {
        let destination: () -> Destination?
        @State var isActive = false
        @ViewBuilder let label: () -> Label
    
        var body: some View {
            NavigationLinkImpl1(destination: destination, isActive: $isActive, label: label)
        }
    }
    
    private struct NavigationLinkImpl1<Destination: View, Label: View>: View {
        let destination: () -> Destination
        @Binding var isActive: Bool
        @ViewBuilder let label: () -> Label
        @State var model = Model()
    
        var body: some View {
            Button(action: action, label: label)
                .introspectNavigationController(customize: handle)
                .id(isActive)
        }
    
        func handle(nav: UINavigationController) {
            if isActive {
                if model.destination == nil {
                    let dest = UIHostingController<Destination>(rootView: destination())
                    nav.pushViewController(dest, animated: true)
                    model.destination = dest
                }
            } else {
                if let dest = model.destination {
                    if let i = nav.viewControllers.lastIndex(of: dest) {
                        nav.setViewControllers(.init(nav.viewControllers.prefix(i + 1)), animated: true)
                    }
                    model.destination = nil
                }
            }
            if isActive != model.contains(nav: nav) { // detect pop
                isActive = model.contains(nav: nav)
            }
        }
    
        final class Model {
            var destination: UIHostingController<Destination>?
            func contains(nav: UINavigationController) -> Bool { destination.map { nav.viewControllers.contains($0) } ?? false }
        }
    
        func action() { isActive = true }
    }
    
    extension NavigationLink {
        init<Destination: View, Label: View>(destination: @autoclosure @escaping () -> Destination, @ViewBuilder label: @escaping () -> Label) {
            self.init(body: NavigationLinkImpl(destination: destination, label: label))
        }
    
        init<Destination: View, Label: View>(destination: @autoclosure @escaping () -> Destination, isActive: Binding<Bool>, @ViewBuilder label: @escaping () -> Label) {
            self.init(body: NavigationLinkImpl1(destination: destination, isActive: isActive, label: label))
        }
    
        init<Destination: View>(_ text: String, destination: @autoclosure @escaping () -> Destination, isActive: Binding<Bool>) {
            self.init(destination: destination(), isActive: isActive) { Text(text) }
        }
    
        init<Destination: View>(_ text: String, destination: @autoclosure @escaping () -> Destination) {
            self.init(destination: destination()) { Text(text) }
        }
    }
    

    Put this in a file, and your existing NavigationLinks will work just fine. Tested in ios 14 and 15

    Login or Signup to reply.
  11. In my case, the NavigationLink didn’t work because of an .onTapGesture I added to dismiss the keyboard.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search