SwiftUI Slide Menu Navigation

To design the MockCon app with the feature set and appearance that the MockCon team wanted, I needed a navigation system that was not supported by SwiftUI's framework. A great intro resource can be found at Programming with Swift, but this tutorial does not expand on the actual implementation of the navigation system. I made the system work with a modified singleton instance that embeds the page inside the navigation structure.


Menu Class

import SwiftUI

struct MenuContent: View {
        
    var menuClose: () -> Void
    @Binding var root: Int
    
    var body: some View {
        VStack(alignment: .trailing) {
            // MARK: IMAGINARY RECTANGLE SPACER
            Rectangle().opacity(0.0).frame(width: 1, height: 35, alignment: .leading)
            HStack {
                Spacer()
                Text("X")
                    .font(.system(size: 23.0))
                    .onTapGesture {
                        self.menuClose()
                }
            }
            // MARK: Imaginary Rectangle Spacer
            Rectangle().opacity(0.0).frame(width: 1, height: 15, alignment: .leading)
            // MARK: EVENTS
            Text("EVENTS")
                .font(.system(size: 16.0))
                .fontWeight(.bold)
                .onTapGesture {
                    self.root = 1
                    self.menuClose()
                }
            .padding(.bottom, 30)
            // MARK: TICKETS
            Text("TICKETS")
                .font(.system(size: 16.0))
                .fontWeight(.bold)
                .onTapGesture {
                    self.root = 2
                    self.menuClose()
                }
            .padding(.bottom, 30)
            // MARK: PHOTOS
            Text("PHOTOS")
                .font(.system(size: 16.0))
                .fontWeight(.bold)
                .onTapGesture {
                    self.root = 3
                    self.menuClose()
                }
            .padding(.bottom, 30)
            // MARK: PRESS
            Text("PRESS")
                .font(.system(size: 16.0))
                .fontWeight(.bold)
                .onTapGesture {
                    self.root = 4
                    self.menuClose()
                }
            .padding(.bottom,30)
            Text("ABOUT")
                .font(.system(size: 16.0))
                .fontWeight(.bold)
                .onTapGesture {
                    self.root = 5
                    self.menuClose()
                }
            .padding(.bottom,30)
            Text("SPONSORS")
            .font(.system(size: 16.0))
            .fontWeight(.bold)
            .onTapGesture {
                self.root = 6
                self.menuClose()
            }
            Spacer()
        }.padding(.trailing, 20)
        .foregroundColor(Color(red: 9.0/255.0, green: 38.0/255.0, blue: 68.0/255.0))
    }
}

struct Menu: View {
    
    let screenWidth = UIScreen.main.bounds.width
    
    let width: CGFloat
    let isOpen: Bool
    var menuClose: () -> Void
    @Binding var root: Int
    
    var body: some View {
        ZStack {
            GeometryReader { _ in
                EmptyView()
            }
            .background(Color.gray.opacity(0.9))
            .opacity(self.isOpen ? 1.0 : 0.0)
            .animation(Animation.easeIn.delay(0))
            .onTapGesture {
                self.menuClose()
            }
            .edgesIgnoringSafeArea(.all)
            HStack {
                Spacer()
                MenuContent(menuClose: menuClose, root: $root)
                    .frame(width: self.width)
                    .background(Color.white)
                    .offset(x: self.isOpen ? 0 : self.width)
                    .animation(.default)
            }.edgesIgnoringSafeArea(.all)
        }
    }
}

The above Menu struct provides an interface between the singleton and the overlaying navigation component. The Binding 'root' variable allows the MenuContent struct to manipulate the below Master stuct. A similar function for showing the menu exists, which is identical to the implementation in the tutorial linked at the beginning of the article.

Master Class

struct Master: View {

    @State var root: Int
    @State var menuOpen: Bool = false
    
    let screenWidth = UIScreen.main.bounds.width
    
    func openMenu() {
        self.menuOpen.toggle()
    }
    
    var body: some View {
        ZStack {
            NavigationView {
                Group {
                    if root == 1 {
                        Events()
                    } else if root == 2 {
                        Tickets()
                    }
                }
                .navigationBarTitle("", displayMode: .inline)
                .navigationBarItems(trailing:
                    Button(action: self.openMenu) {
                        Image(systemName: "line.horizontal.3")
                            .imageScale(.large)
                            .foregroundColor(Color(red: 9.0/255, green: 38.0/255.0, blue: 68.0/255.0))
                    })
            }
            Menu(width: 2.35*screenWidth/5,
                    isOpen: self.menuOpen,
                    menuClose: openMenu, root: $root)
        }
    }
    
}

struct Master_Previews: PreviewProvider {
    static var previews: some View {
        Master(root: 1)
    }
}

Given that a struct must return a single view. It occurred to me an if-else ladder would be a capable way of passing a value from the menu and forcing a redraw of the screen on the main area of the screen. If one were to construct a view from the menu, only the area the menu covers would be affected - leading to undesirable view stacking.

This solution utilizes a State variable to always force the value of the master to return a screen, and passes this variable to the Menu structs to be adjusted. When it is changed, the primary view in the Master struct is reassigned and redrawn. This way only a single menu and master view ever exist in the app life cycle rather than attempting to redraw the menu and navigation stack each time.