Malav Soni
Malav Soni

Reputation: 2848

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.

Upvotes: 2

Views: 2751

Answers (2)

Asperi
Asperi

Reputation: 258057

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)
    }
}

Upvotes: 4

Ho Si Tuan
Ho Si Tuan

Reputation: 540

You should use this library KYDrawerController

Declare ContentView and MenuView in SceneDelegate:

import KYDrawerController
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    var window: UIWindow?
    let drawerController = KYDrawerController(drawerDirection: .left, drawerWidth: 300)
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        
        drawerController.mainViewController = UIHostingController(rootView: ContentView())
        drawerController.drawerViewController = UIHostingController(rootView: LeftNavigationView()
                                                                    
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            let vc = drawerController
            vc.view.frame = window.bounds
            window.rootViewController = vc
            self.window = window
            (UIApplication.shared.delegate as? AppDelegate)?.self.window = window
            window.makeKeyAndVisible()
        }
        //...
    }
    
    //...
}

Upvotes: 1

Related Questions