0

I'm trying to take my app and migrate my opening NavigationView which has been deprecated to the new NavigationStack.

My opening screen looks like: enter image description here

and the code that presents it is:

import SwiftUI

struct MainAppView: View {
    
    @State var listSelection: Int = 1
    @Binding var isLoggedIn: Bool
    
    var body: some View {
        
        NavigationView {
            
            if DBManager.shared.checkDatabaseFileExists()
            {
                SideListView(listSelection: $listSelection)
                HStack {
                    if listSelection == 1
                    {
                        AccountsListView()
                    }
                    if listSelection == 2
                    {
                        SetAsideBalanceListView()
                    }
                    if listSelection == 3
                    {
                        BankBalanceListView()
                    }
                    if listSelection == 4
                    {
                        SetupMenuView()
                    }
                    if listSelection == 5
                    {
                       ReportsListView()
                    }
                } // END HSTACK
                
            } else {
                Text("NO DATABASE FOUND!!!!")
                    .font(.system(size: 60))
            } // END OUTTER IF/ELSE DATABASE FOUND
            
        } // END NAVIGATION VIEW
    } // END BODY VIEW
} // END STRUCT VIEW

I did the simple thing of changing NavigationView to NavigationStack and while it compiles, it looks wrong:

enter image description here

Chris... the solution that I implemented using your suggestion is working and would be acceptable, except that the navigation behavior seems to have changed with NavigationSplitView and NavigationStack. When a selection is made in the left pane the corresponding view appears in the right pane. The view in the right pane has a NavigationLink to a subview. This works and the subview has the back button. What I noticed is that with NavigationView if the user in a subview clicks on the selection in the left pane, the view immediately pops the appropriate selected view to the right pane clearing the subview of some other selection that is showing. But in this model, using NavigationStack on the selected view, if the subview is showing for a given selection, clicking on the left pane selection has no effect until the existing subview is back buttoned to the parent view at which time the selected view is presented.

Like this: enter image description here

And then selected the sub view looks OK like this:

enter image description here

But when I select in the left pane another selection like this:

enter image description here

Using NavigationView the subview for SetAside would immediately pop but instead only shows after I use the back button on the sub view...

enter image description here

UPDATES:

Here is the code I've implemented for the MainAppView.swift

import SwiftUI

struct MainAppView: View {
    
    @State var listSelection: Int? = 1
    @Binding var isLoggedIn: Bool
    
    var body: some View {
        
        
        if DBManager.shared.checkDatabaseFileExists()
        {
                NavigationSplitView {
                    SideListView(listSelection: $listSelection)
                } detail: {
                    NavigationStack {
                        switch listSelection {
                        case 1: AccountsListView()
                        case 2: SetAsideBalanceListView()
                        case 3: BankBalanceListView()
                        case 4: SetupMenuView()
                        case 5: ReportsListView()
                        default: Text("Select View")
                        }
                    } // END NAVIGATION STACK
                } // END NAVIGATION SPLIT VIEW DETAIL
        } else {
            Text("NO DATABASE FOUND!!!!")
                .font(.system(size: 60))
        } // END OUTTER IF/ELSE DATABASE FOUND
        
    } // END BODY VIEW'
} // END STRUCT VIEW

Here is the view code for the SetAsideBalanceListView you can comment out the DB function calls and get an idea of the code. They are all somewhat clones of each other

import SwiftUI

struct SetAsideBalanceListView: View {
    
    var accounts: [Accounts.AccountRecord] {
        var acctRec = [Accounts.AccountRecord]()
        acctRec = Accounts.shared.selectAllAccounts()
        return acctRec
    }
    
    var body: some View {

//        NavigationStack  {
            VStack {
                CustomDivider(horizontalpadding: 0, thickness: 1)
                
                ForEach (accounts, id: \.self) { accountRec in
                    let result = Budget.shared.getBudgetMonthAndYearForView(withAccountCode: accountRec.account_code)
                    
                    NavigationLink(destination:   SetAsideBalanceView(account_code: accountRec.account_code, currentBudgetYear: result.viewYear)){
                        HStack {
                            Image(systemName: "dollarsign.circle")
                                .foregroundColor(.white)
                                .background(Color("iconBlue"))
                                .cornerRadius(25)
                                .padding(.leading, 15)
                            Text(accountRec.account_name)
                            Spacer()
                        }
                    }
                    .foregroundColor(.primary)
                    Divider().background(Color.primary)
                }// END FOR EACH
                
            } // END VSTACK
            .font(.title2)
            .frame(minWidth: 0,
                   maxWidth: .infinity,
                   minHeight: 0,
                   maxHeight: .infinity,
                   alignment: .topLeading
            )
            .padding(.top, 20)
            .navigationTitle("Managing Your Money - Set Aside")
//        } // END NAVIGATION STACK
    } // END BODY VIEW
} // END STRUCT VIEW

It looks like this:

enter image description here And here is the subview that the view above displays.. This is the one that needs a full "back button" to see the selected view.. If you clone these to make the other subviews you'll get the results (I hope:-))

import SwiftUI

struct SetAsideBalanceView: View {
    
    @State var account_code:  Int
    @State var currentBudgetYear: Int
    @State var totalBalances: Double = 0.00

    // Array of SetAside Balance records for View List
    var setAsideBalances: [SetAsideBalances.SetAsideBalancesRecord]
    {
        return SetAsideBalances.shared.selectSetAsideBalancesForAccount(withAccountCode: self.account_code)
    }
    
    var body: some View {
        
        GeometryReader { gr in
            let viewWidth = gr.size.width * 1
            
            let columns = [
                GridItem(.fixed(viewWidth * 0.60), alignment: .leading),
                GridItem(.fixed(viewWidth * 0.30), alignment: .trailing)
            ]
            
            VStack {
                if setAsideBalances.count > 0 {
                    SetAsideBalanceHeader(accountName: setAsideBalances[0].account_name!, budgetYear: currentBudgetYear)
                    
                    ScrollView {
                        ForEach (setAsideBalances, id: \.self) { setAsideRecord in
                            SetAsideBalancesRow(accountCode: account_code, setAsideBalanceCode: setAsideRecord.set_aside_code, description: setAsideRecord.description, set_aside_balance: setAsideRecord.set_aside_balance, currentBudgetYear: currentBudgetYear)
                                .frame(height: 45)
                        }
                    }
                    .frame(height: CGFloat((setAsideBalances.count + 1) * 45))
                    
                    LazyVGrid(columns: columns, spacing: 0) {
                        
                        Text("TOTAL BALANCES")
                            .padding(.leading,  20)
                        
                        Text("\(NumberFormatter.formatAsCurrency(value: totalBalances))")
                            .foregroundColor((totalBalances < 0 ) ? Color("negative") : nil)
                    }
                    .frame(maxWidth: .infinity)
                    .frame(height: 55)
                    .border(Color.primary)
                    .foregroundColor(.black)
                    .background(Rectangle().fill(Color("lightBlue")))
                    .font(.title2)
                    
                    Spacer()
                }
            } // END VSTACK
            .font(.title2)
            .frame(minWidth: 0,
                   maxWidth: .infinity,
                   minHeight: 0,
                   maxHeight: .infinity,
                   alignment: .topLeading
            )
            .padding(.top, 5)
            .onAppear {
                self.totalBalances = SetAsideBalances.shared.sumSetAsideBalancesForAccount(withAccountCode: self.account_code)
            }
            .font(.title2)
            .navigationTitle("Managing Your Money - Set Aside")
            .navigationBarTitleDisplayMode(.inline)
            .ignoresSafeArea(edges: .bottom)
        }

    } // END BODY VIEW
    
} // END STRUCT VIEW

This is what the subview looks like:

enter image description here

3 Answers 3

1

I think I found it. We have to update the NavigationLink in the subview also to the new logic.

In SetAsideBalanceListView replace this:

ForEach (accounts, id: \.self) { accountRec in
                    let result = Budget.shared.getBudgetMonthAndYearForView(withAccountCode: accountRec.account_code)
                    
                    NavigationLink(destination:   SetAsideBalanceView(account_code: accountRec.account_code, currentBudgetYear: result.viewYear)){
                        HStack {
                            Image(systemName: "dollarsign.circle")
                                .foregroundColor(.white)
                                .background(Color("iconBlue"))
                                .cornerRadius(25)
                                .padding(.leading, 15)
                            Text(accountRec.account_name)
                            Spacer()
                        }
                    }
                    .foregroundColor(.primary)
                    Divider().background(Color.primary)
                }// END FOR EACH

with this:

ForEach (accounts, id: \.self) { accountRec in
                    let result = Budget.shared.getBudgetMonthAndYearForView(withAccountCode: accountRec.account_code)
                    
                    NavigationLink(value: accountRec) { // HERE
                        HStack {
                            Image(systemName: "dollarsign.circle")
                                .foregroundColor(.white)
                                .background(Color("iconBlue"))
                                .cornerRadius(25)
                                .padding(.leading, 15)
                            Text(accountRec.account_name)
                            Spacer()
                        }
                    }
                    .foregroundColor(.primary)
                    Divider().background(Color.primary)
                }// END FOR EACH
                // HERE
                .navigationDestination(for: AccountRecord.self) { accountRec in
                    SetAsideBalanceView(account_code: accountRec.account_code, currentBudgetYear: result.viewYear)
                 }
Sign up to request clarification or add additional context in comments.

Comments

1

You want to switch to NavigationSplitView(sidebar: content:). As the name implies it has two elements: 1 the sidebar, and 2 the content area.

I also would like to suggest to exchange the many if statements for the view selection with a switch statement on listSelection.

    @State var listSelection: Int = 1
    @Binding var isLoggedIn: Bool
    
    var body: some View {
        
        if DBManager.shared.checkDatabaseFileExists()
        {
        
            NavigationSplitView {
                SideListView(listSelection: $listSelection)
            } detail: {
                switch listSelection {
                case 1: AccountsListView()
                case 2: SetAsideBalanceListView()
                case 3: BankBalanceListView()
                case 4: SetupMenuView()
                case 5: ReportsListView()
                default: Text("Select View")
                }
            }
            
        } else {
            Text("NO DATABASE FOUND!!!!")
                .font(.system(size: 60))
        } // END OUTTER IF/ELSE DATABASE FOUND
        
    } // END BODY VIEW

2 Comments

Thanks for taking the time to look at this for me. Your suggestion to switch to "switch" statements (pun intended :-) ) is well received. I've been coding since 1979 through a multitude of languages over the past 40+ years. Swift has been a great challenge for me as it is the most versatile and powerful language yet. Anyway, your solution worked fine. Well, almost. The presentation is exactly what it should be but after implementing it the downstream views don't seem to automatically implement the "back" functionality. I'll work on that. Thanks again
You're welcome! I started with Pascal a long time ago ;) The back functionality issue might be connected to the now changed logic of navigation stacks and Navigation links ... I have been mainly doing macOS recently, so I forgot about that. Let me look into it ...
1

extended version including detail links.

struct ContentView: View {
    
    @State var listSelection: Int? = 1
//    @Binding var isLoggedIn: Bool
    
    var body: some View {
        
//        if DBManager.shared.checkDatabaseFileExists()
//        {
        
            NavigationSplitView {
                List(selection: $listSelection) {
                    Label("Accounts", systemImage: "dollarsign.circle").tag(1)
                    Label("Set Aside", systemImage: "folder").tag(2)
                    Label("Bank Bal.", systemImage: "line.2.horizontal.decrease.circle").tag(3)
                    Label("Setup", systemImage: "gear").tag(4)
                    Label("Reports", systemImage: "gear").tag(5)
                }

            } detail: {
                switch listSelection {
                case 1: SubView(title: "Accounts")
                case 2: SubView(title: "Set Aside")
                case 3: SubView(title: "Bank Balance")
                case 4: SubView(title: "Setup")
                case 5: SubView(title: "Reports")
                default: Text("Select View")
                }
            }
            
//        } else {
//            Text("NO DATABASE FOUND!!!!")
//                .font(.system(size: 60))
//        } // END OUTTER IF/ELSE DATABASE FOUND
        
    } // END BODY VIEW
} // END STRUCT VIEW


struct SubView: View {
    
    let title: String
    
    var body: some View {
        NavigationStack {
            List(0..<6, id: \.self) { nr in
                NavigationLink("\(title) Subview \(nr)", value: nr) // select
            }
            .navigationDestination(for: Int.self) { nr in // define the destination (Int.self refers to type of selection)
                DetailView(item: nr)
            }
        }
        .navigationBarTitle(title)
    }
}


struct DetailView: View {
    
    let item: Int
    
    var body: some View {
        Text("This is the detail view for item \(item)")
    }
}

4 Comments

Wow thank you for taking this much more time for me. I'll be putting this in tomorrow morning. I'm sure that this will help. I'd been doing my research / reading on this and your solution is right in line with what I was seeing...
as you know its always much more fun to solve other peoples problems :) And I learn too!
So, my final solution was to use your example exactly as initially presented above, but using your second example I found that in my subviews I just needed to add NavigationStack { code goes here } surrounding all my other code and "poof" the back button appears..
I can't see this behavior in my code example ... can you share a simplified code of one detail view?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.