Search code examples
iosswiftswiftuiswiftui-navigationlinkswiftui-navigationstack

Are there any way to work using NavigationStack with NavigationLink to support applications below iOS 16?


In some points of the code, I need to use this:

    NavigationLink(
        destination: LoginViewScreen(),
        isActive: $navToLogin
    ) { EmptyView() }

... because my navigation is not using a button, I use only the property as trigger to navigate.

I'm receiving this warning:

use NavigationLink(value:label:) inside a List within a NavigationStack or NavigationSplitView

The problem is that minimum iOS target is 14.0 and I'm not able to use NavigationStack because it is for iOS 16.0.

I would like something like that, where I can use the both ways. But I am not being able, because the property NavigationPath needs to be a @State, and the states need to be a property of the class, and if the property is iOS > 16, all the class is > 16 and a cannot instanciate it in my application

@State var navToLogin: Bool = false
@EnvironmentObject var navigationViewModel: NavigatorViewModel

var body: some View  {
    NavigationView {
        VStack {
            ExampleView()
                .onAppear() {
                    Task {
                        try? await Task.sleep(nanoseconds: 5000000000)
                        navigateToLogin()
                    }
                }
            navigationToLogin
        }
    }
}

var navigationToLogin: some View {
    NavigationLink(
        destination: LoginViewScreen(),
        isActive: $navToLogin
    ) { EmptyView() }
}

private func navigateToLogin() {
    if #available(iOS 16, *) {
        navigationViewModel.navigateTo(.loginScreen)
    } else {
        navToLogin.toggle()
    }
}

Does anyone know how to work well with it ?

Or is there any other way ?


Solution

  • I asked this in the R/SwiftUI too and I received a response that may help who is having the same problem.

    The response:

    https://www.reddit.com/r/SwiftUI/comments/1b8284a/comment/ktmuxoe/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button

    The solution:

        public struct NavigationViewStack<V>: View where V: View {
    
        @ViewBuilder private let content: () -> V
    
        public init(content: @escaping () -> V) {
            self.content = content
        }
    
        public var body: some View {
            if #available(iOS 16, *) {
                NavigationStack { content() }
            } else {
                NavigationView { content() }
            }
        }
    }
    
    public extension View {
        @ViewBuilder
        func navigationDestinationWrapper<V>(isPresented: Binding<Bool>, @ViewBuilder destination: () -> V) -> some View where V: View {
            if #available(iOS 16, *) {
                self.navigationDestination(isPresented: isPresented, destination: destination)
            } else {
                ZStack {
                    NavigationLink(isActive: isPresented, destination: destination, label: {
                        EmptyView()
                    })
                    self
                }
            }
        }
    
        @ViewBuilder
        func navigationDestinationWrapper<D, C>(item: Binding<D?>, @ViewBuilder destination: @escaping (D) -> C) -> some View where D: Hashable, C: View {
            if #available(iOS 17, *) {
                self.navigationDestination(item: item, destination: destination)
            } else {
                ZStack {
                    NavigationLink(
                        destination: generateDestination(item, destination),
                        isActive: Binding<Bool>(
                            get: { item.wrappedValue != nil },
                            set: { _ in
                                item.wrappedValue = nil
                            }
                        ),
                        label: { EmptyView() }
                    )
                    self
                }
            }
        }
    
        @ViewBuilder
        private func generateDestination<D, C>(_ item: Binding<D?>, @ViewBuilder _ destination: @escaping (D) -> C) -> some View where D: Hashable, C: View {
            if let unwrappedItem = item.wrappedValue {
                destination(unwrappedItem)
            } else {
                EmptyView()
            }
        }
    }
    

    Example:

    NavigationViewStack {
      Text("First Page")
        .navigationDestinationWrapper(isPresented: $presentSecondPage, destination: {
            Text("Second Page")
        })
    }
    

    Here, we create the NavigationViewStack to wrap NavigationStack and NavigationView.

    The extensions will wrap the functions of NavigationStack to have a similar functionally in NavigationView.

    The best solution is upgrade to the minimum iOS version to 16.0, but it will be useful to not broke when the NavigationView be deprecated.