SwiftUI SideMenu Navigation Issue

I am currently refactoring my app's side menu to be more like Twitter's. I have the UI down in terms of how the side menu looks and appears, but the issue is navigating to a view from the side menu. The views that a user can go to from the side menu are a mix of SwiftUI views & UIKit View Controllers. As of right now, when a user navigates to a view from the side menu, it presents it modally as a sheet. I want it to have regular navigation, where the user goes to the view displayed in full screen and can tap on the back button to go back to the previous view.

Here is the associated code:

//
//  SideMenuView.swift
//  uSTADIUM
//
//  Created by Omar Hegazy on 6/13/24.
//  Copyright © 2024 uSTADIUM. All rights reserved.
//

import SwiftUI

struct SideMenuView: View {
    @ObservedObject var viewModel: SideMenuViewModel
    
    var body: some View {
        NavigationStack {
            ZStack {
                Color.containerBackground
                    .edgesIgnoringSafeArea(.all)
                ScrollView {
                    VStack(alignment: .leading, spacing: 16) {
                        // User profile section
                        if let user = viewModel.user {
                            HStack(spacing: 12) {
                                ProfileImageView(url: user.getProfileImageURL())
                                    .frame(width: 50, height: 50)
                                    .clipShape(Circle())
                                VStack(alignment: .leading, spacing: 4) {
                                    Text(user.nickname)
                                        .font(.headline)
                                    user.attributedFullAt()
                                        .font(.subheadline)
                                        .foregroundColor(.gray)
                                }
                                Spacer()
                            }
                            .padding(.horizontal)
                            .padding(.vertical, 8)
                            .background(Color.containerBackground)
                            .cornerRadius(8)
                            .padding(.horizontal)
                        }
                        
                        // MARK: - Side Menu Sections
                        VStack(alignment: .leading, spacing: 8) {
                            SideMenuSection(header: "PROFILE", options: viewModel.profileOptions, onTap: viewModel.handleProfileOptionTap)
                            SideMenuSection(header: "COOL STUFF", options: viewModel.coolStuffOptions, onTap: viewModel.handleCoolStuffOptionTap)
                            if viewModel.isAdmin {
                                SideMenuSection(header: "ADMIN", options: viewModel.adminOptions, onTap: viewModel.handleAdminOptionTap)
                            }
                        }
                        .padding(.horizontal)
                    }
                }
            }
        }
        .onAppear {
            viewModel.navigateTo = { vc in
                if let rootVC = UIApplication.shared.windows.first?.rootViewController {
                    rootVC.dismiss(animated: true) {
                        if let navVC = rootVC as? UINavigationController {
                            navVC.pushViewController(vc, animated: true)
                        } else {
                            rootVC.present(vc, animated: true, completion: nil)
                        }
                    }
                }
            }
        }
    }
}

struct SideMenuSection: View {
    var header: String
    var options: [SideMenuOBJ]
    var onTap: (SideMenuOBJ) -> Void
    
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(header)
                .font(.headline)
                .padding(.leading)
                .padding(.top, 8)
            ForEach(options, id: \.label) { option in
                MenuOptionRow(option: option)
                    .onTapGesture {
                        onTap(option)
                        print("Option chosen: \(option.label)")
                    }
            }
        }
        .background(Color.containerBackground)
        .cornerRadius(8)
    }
}

struct MenuOptionRow: View {
    var option: SideMenuOBJ
    
    var body: some View {
        HStack {
            Image(uiImage: option.image)
                .renderingMode(.template)
                .foregroundColor(.primary)
                .frame(width: 24, height: 24)
            Text(option.label)
                .foregroundColor(.primary)
                .font(.body)
            Spacer()
        }
        .padding()
    }
}

//
//  SideMenuViewModel.swift
//  uSTADIUM
//
//  Created by Omar Hegazy on 6/13/24.
//  Copyright © 2024 uSTADIUM. All rights reserved.
//

import SwiftUI
import UIKit

class SideMenuViewModel: ObservableObject {
    @Published var user: UserOBJ?
    @Published var profileOptions: [SideMenuOBJ] = SideMenuRepository.profile()
    @Published var coolStuffOptions: [SideMenuOBJ] = SideMenuRepository.coolStuff()
    @Published var adminOptions: [SideMenuOBJ] = SideMenuRepository.admin()
    @Published var isAdmin: Bool = AuthService.shared.isAdmin
    
    var navigateTo: ((UIViewController) -> Void)?
    
    init() {
        user = AuthService.shared.user
    }
    
    func handleProfileOptionTap(option: SideMenuOBJ) {
        switch option.label {
        case "My Profile":
            if let user = AuthService.shared.user {
                let profileView: UIViewController = UIHostingController(rootView: ProfileView(user: user))
                profileView.extendedLayoutIncludesOpaqueBars = true
                profileView.hidesBottomBarWhenPushed = true
                navigateTo?(profileView)
            }
        case "Edit Profile":
            if let user = AuthService.shared.user {
                let editProfile = EditProfileTableViewController()
                editProfile.hidesBottomBarWhenPushed = true
                editProfile.service = UserPool.shared.request(for: user)
                navigateTo?(UINavigationController(rootViewController: editProfile))
            }
        case "Saved":
            let savedView = UIHostingController(rootView: SavedPostsView())
            navigateTo?(savedView)
        case "Account":
            if let vc = AccountSummaryFeature.getVC() as? UIViewController {
                navigateTo?(vc)
            }
        case "Settings":
            let settingsVC = UIHostingController(rootView: SettingsView())
            settingsVC.isModalInPresentation = true
            navigateTo?(settingsVC)
        case "My Takes History":
            navigateTo?(UIHostingController(rootView: UserTakesHistoryView(historyVM: UserTakesHistoryViewModel(selectedContainer: .liveTakes))))
        default:
            break
        }
    }
    
    func handleCoolStuffOptionTap(option: SideMenuOBJ) {
        if let vc = option.vc {
            navigateTo?(vc)
        }
    }
    
    func handleAdminOptionTap(option: SideMenuOBJ) {
        switch option.label {
        case "Users":
            let usersAdmin = UsersAdminViewController()
            navigateTo?(UINavigationController(rootViewController: usersAdmin))
        case "Takes":
            let takesVC = AdminTakesVC()
            navigateTo?(UINavigationController(rootViewController: takesVC))
        case "Cashout":
            let cashoutVC = CashoutAdminVC()
            navigateTo?(UINavigationController(rootViewController: cashoutVC))
        case "Feature Toggles":
            let toggleView = FeatureTogglesView()
            navigateTo?(UIHostingController(rootView: toggleView))
        case "Promo Codes":
            let promoCodeView = PromoCodeList()
            navigateTo?(UIHostingController(rootView: promoCodeView))
        case "Live Odds":
            let oddsView = UIHostingController(rootView: TheOddsView(pickerVM: OddsPickerViewModel(selectedContainer: .nfl)))
            oddsView.hidesBottomBarWhenPushed = true
            oddsView.extendedLayoutIncludesOpaqueBars = true
            navigateTo?(oddsView)
        default:
            break
        }
    }
}

How can I modify the navigation logic to be like Twitter's? I've been stuck for days trying to find a fix but it has been a struggle.

Answered by Hegazy2468 in 792591022

This issue has since been fixed

Accepted Answer

This issue has since been fixed

SwiftUI SideMenu Navigation Issue
 
 
Q