Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI NavigationLink pops out by itself

I have a simple use case where a screen pushes another screen using the NavigationLink. There is a strange behaviour iOS 14.5 beta (1, 2, 3), where the pushed screen is popped just after being pushed.

I manage to create a sample app where I reproduce it. I believe the cause is the presence of @Environment(\.presentationMode) that seem to re-create the view and it causes the pushed view to be popped.

The exact same cod works fine in Xcode 12 / iOS 14.4

enter image description here

Here is a sample code.

import SwiftUI

public struct FirstScreen: View {
    public init() {}
    public var body: some View {
        NavigationView {
            List {
                row
                row
                row
            }
        }
    }
    private var row: some View {
        NavigationLink(destination: SecondScreen()) {
            Text("Row")
        }
    }
}

struct SecondScreen: View {

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    public var body: some View {
        VStack(spacing: 10) {
            NavigationLink(destination: thirdScreenA) {
                Text("Link to Third Screen A")
            }

            NavigationLink(destination: thirdScreenB) {
                Text("Link to Third Screen B")
            }

            Button("Go back", action: { presentationMode.wrappedValue.dismiss() })
        }

    }

    var thirdScreenA: some View {
        Text("thirdScreenA")
    }

    var thirdScreenB: some View {
        Text("thirdScreenB")
    }
}


struct FirstScreen_Previews: PreviewProvider {
    static var previews: some View {
        FirstScreen()
    }
}
like image 548
Jan Avatar asked Mar 10 '21 07:03

Jan


4 Answers

I just added .navigationViewStyle(StackNavigationViewStyle()) & bug vanishes

Example :--

 NavigationView {
         content
}
.navigationViewStyle(StackNavigationViewStyle())
like image 139
Jack Avatar answered Oct 17 '22 13:10

Jack


Looks like a bug when there is exactly 2 NavigationLinks. If you add another empty link it goes away:

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

More details: https://forums.swift.org/t/14-5-beta3-navigationlink-unexpected-pop/45279

like image 20
Paul Budzinsky Avatar answered Oct 17 '22 13:10

Paul Budzinsky


Applying .isDetailLink(false) to your NavigationLink may do the job. By default it is true. From the docs:

This method sets the behavior when the navigation link is used in a multi-column navigation view, such as DoubleColumnNavigationViewStyle. If isDetailLink is true, performing the link in the primary column sets the contents of the secondary (detail) column to be the link’s destination view. If isDetailLink is false, the link navigates to the destination view within the primary column.

like image 8
Jiropole Avatar answered Oct 17 '22 13:10

Jiropole


In my case I had:

(1) A tab view with a root view that had 2 navigation starting points

(2) the first and second navigations have quite a lot of nested views

(3) adding .navigationViewStyle(StackNavigationViewStyle() fixed the issue only for root views that had only one starting point of navigation

(4) as soon as I had another navigation coming from the same view the problem reappeared (only for iOS 14.5 to 14.8)

(5) solely adding the

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

didn't work for me

(6) On my project I have a coordinator that is responsible for creating the viewModels (that are published properties) and I have a ContainerView that will handle all the navigation. The navigation links are created based on the existence or not of a viewModel, so if a viewModel exists that view will be presented and when the view is dismissed the viewModel will be set to nil. (I'll add the code that does that at the end)

(7) for some weird reason, adding a third navigation to the view that is responsible for the navigation stopped my container view to be re-rendered and the view to stop being popped back.

Simply adding the NavigationLink to the container didn't work, but using the modifier I'm using and setting the destination to be the Empty NavigationLink did.

This is the code that is used for the navigation:

    func navigation<Item, Destination: View>(
        item: Binding<Item?>,
        @ViewBuilder destination: (Item) -> Destination
    ) -> some View {
        let isActive = Binding(
            get: { item.wrappedValue != nil },
            set: { value in
                if !value {
                    item.wrappedValue = nil
                }
            }
        )
        return navigation(isActive: isActive) {
            item.wrappedValue.map(destination)
        }
    }

    func navigation<Destination: View>(
        isActive: Binding<Bool>,
        @ViewBuilder destination: () -> Destination
    ) -> some View {
        overlay(
            NavigationLink(
                destination: isActive.wrappedValue ? destination() : nil,
                isActive: isActive,
                label: { EmptyView() }
            )
        )
    }

This is how my container view works. My coordinator has Published properties that are optional viewModels and the existence or not of that viewModel is what triggers the isActive value on the navigation link. Adding that last .navigation worked. My empty container just has the empty navigationLink

My navigation container

like image 3
Marcela Ceneviva Auslenter Avatar answered Oct 17 '22 14:10

Marcela Ceneviva Auslenter