SwiftUI NavigationView issue with Parent-Child-Grandchild scenario when attempting grandchild previous next navigation

641 views Asked by At

I am using SwiftUI in a project that requires a model with a 'one to many to many' entity structure. I use a navigation view at the parent level with navigation links at parent and child levels.

This works well in that I can:

  1. Select a child at parent level to navigate to the child
  2. Select a grandchild at child level to navigate to the grandchild

I have an extra requirement - to provide next and previous buttons at grandchild level. I do this using the tag/selection feature provided for navigation links. I give each grandchild a numeric index tag and then call passed in functions at child level to change the selected child view by adding or subtracting one from the index.

The problem is that when I navigate to the next or previous grandchild the 'back' link is changed to be either the last grandchild visited, or the word 'Back' instead of the child view. If I then select the 'back' link on that view there is an immediate transition to a previously visited grandchild followed by an immediate transition to the child view.

The included code is a working project which shows a simpler example using a 'one to one to many' scenario which features the same problem.

If I change the code to a simpler parent child model with next and previous at child level this all works as expected so the problem is due to the three levels. Any help with a solution would be very welcome.

//
//  ContentView.swift
//  NextPreviousTest
//
//  Created by Mike Cooper on 26/09/2020.
//
import SwiftUI

struct ContentView: View {
    
    var body: some View {
        NavigationView {
            VStack {
                Spacer()
                NavigationLink(destination: ChildView()) {
                    Text("Child View")
                }.buttonStyle(PlainButtonStyle())
                Spacer()
            }.navigationBarTitle(Text("Parent"), displayMode: .inline)
        }
    }
        
}

struct ChildView: View {
    @State private var array = [0, 1, 2, 3, 4, 5]
    @State private var selectedGrandChild: String? = nil
    
    var body: some View {
        ScrollView {
            ForEach(0..<array.count) { i in
                NavigationLink(destination: GrandChildView(viewIndex: self.array[i], prevFunction: childView_prevFunction,
                                                      nextFunction: childView_nextFunction
                )
                , tag: String(self.array[i]), selection: $selectedGrandChild) {
                    Text("Grandchild \(self.array[i])").padding()
                }.buttonStyle(PlainButtonStyle())
            }
        }.navigationBarTitle(Text("Child"), displayMode: .inline)
    }
    
    private func childView_prevFunction() {
        let tag = Int(self.selectedGrandChild ?? "0")
        if tag! > 0 {
            self.selectedGrandChild = String(tag! - 1)
            print(String(tag! - 1))
        }
    }
    
    private func childView_nextFunction() {
        let tag = Int(self.selectedGrandChild ?? "0")
        if tag! < self.array.count - 1 {
            self.selectedGrandChild = String(tag! + 1)
            print(String(tag! + 1))
        }
    }
    
}

struct GrandChildView: View {
    
    let viewIndex: Int
    var prevFunction: () -> Void
    var nextFunction: () -> Void
    
    var body: some View {
        VStack{
            HStack {
                Button(action: {
                    self.prevFunction()
                }) {
                    Image(systemName: "chevron.left")
                }
                Text("Grandchild \(viewIndex)")
                Button(action: {
                    self.nextFunction()
                }) {
                    Image(systemName: "chevron.right")
                }
            }.font(.headline)
        }.navigationBarTitle("Grandchild \(viewIndex)").font(.caption)
    }
}
0

There are 0 answers