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.