Search code examples
swiftanimationswiftuinavigationhamburger-menu

Hamburger Navigation Menu Slide Animation in Swiftui


I am trying to build a navigation drawer with slide animation for left menu content and opacity animation for the background of the menu.

The code below works fine for me except for the animation. I am not sure where exactly the animation went wrong and it's not working.

Here is my code.

struct LeftNavigationView:View {
    @EnvironmentObject var viewModel:ViewModel
    var body: some View {
        ZStack {
            Color.black.opacity(0.8)
                .ignoresSafeArea()
                .transition(.opacity)
                .animation(.default)
            VStack {
                Button(action: {
                    self.viewModel.isLeftMenuVisible.toggle()
                }, label: {
                    Text("Close Me")
                })
            }
            .frame(maxWidth:.infinity, maxHeight: .infinity)
            .background(Color.white) 
            .cornerRadius(10)
            .padding(.trailing)
            .padding(.trailing)
            .padding(.trailing)
            .padding(.trailing)
            .transition(
                .asymmetric(
                    insertion: .move(edge: .leading),
                    removal: .move(edge: .leading)
                )
            )
            .animation(.default)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .animation(.default)
    }
}


class ViewModel: ObservableObject {
    @Published var isLeftMenuVisible:Bool = false
}

struct ContentView: View {
    @StateObject var viewModel:ViewModel = ViewModel()
    var body: some View {
        ZStack {
            NavigationView {
                VStack(alignment:.leading) {
                    Button(action: {
                        self.viewModel.isLeftMenuVisible.toggle()
                    }, label: {
                        Text("Button")
                    })
                }.padding(.horizontal)
                .navigationTitle("ContentView")
            }
            if self.viewModel.isLeftMenuVisible {
                LeftNavigationView()
            }
        }.environmentObject(self.viewModel)
    }
}

Any help will be appreciated.


Solution

  • You almost there. It is better to keep control over appearance/disappearance inside menu view. Find below fixed parts, places are highlighted with comments in code.

    Tested with Xcode 12.5 / iOS 14.5

    Note: demo prepared with turned on "Simulator > Debug > Slow Animations" for better visibility

    demo

    struct LeftNavigationView:View {
        @EnvironmentObject var viewModel:ViewModel
        var body: some View {
            ZStack {
                if self.viewModel.isLeftMenuVisible {     // << here !!
                    Color.black.opacity(0.8)
                        .ignoresSafeArea()
                        .transition(.opacity)
    
                    VStack {
                        Button(action: {
                            self.viewModel.isLeftMenuVisible.toggle()
                        }, label: {
                            Text("Close Me")
                        })
                    }
                    .frame(maxWidth:.infinity, maxHeight: .infinity)
                    .background(Color.white)
                    .cornerRadius(10)
                    .padding(.trailing)
                    .padding(.trailing)
                    .padding(.trailing)
                    .padding(.trailing)
                    .transition(
                        .asymmetric(
                            insertion: .move(edge: .leading),
                            removal: .move(edge: .leading)
                        )
                    ).zIndex(1)  // << force keep at top where removed!!
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .animation(.default, value: self.viewModel.isLeftMenuVisible)  // << here !!
        }
    }
    
    struct ContentView: View {
        @StateObject var viewModel = ViewModel()
        var body: some View {
            ZStack {
                NavigationView {
                    VStack(alignment:.leading) {
                        Button(action: {
                            self.viewModel.isLeftMenuVisible.toggle()
                        }, label: {
                            Text("Button")
                        })
                    }.padding(.horizontal)
                    .navigationTitle("ContentView")
                }
    
                // included here, everything else is managed inside (!) view
                LeftNavigationView()
    
            }.environmentObject(self.viewModel)
        }
    }