Search code examples
iosswiftuiviewuipangesturerecognizer

Hamburger Menu UIPanGestureRecogniser


I am trying to make a hamburger menu for my app programmatically and using no plugins. I am starting with the basics, so just trying to get 2 UIViews working together when the user swipes right. The 2 views are the main view and the hamburger menu.

So far the UIViews load in the correct place and when the user swipes right the sidebar slides along. However, when the user lets go the UIView also slides up to the center of the UIView is pinned to the top of the screen. See images below:

enter image description here

Once it gets lost up there I cannot pull it back down. I can still swipe left and right but the center stays constrained to the top of the screen.

I've looked through my code and cannot see what I am doing wrong here?

Here is my code:

    class gestureSwipe: UIViewController, UIGestureRecognizerDelegate {

    let screenHeight = UIScreen.main.bounds.height
    let screenWidth = UIScreen.main.bounds.width

    var trayOriginalCenter: CGPoint!
    var sideBarSwipeLeftOffset: CGFloat!
    var siderBarSwipeRight: CGPoint!
    var sideBarSwipeLeft: CGPoint!

    let sideBarUIView: UIView! = {
        let sideBarUIView = UIView()
        sideBarUIView.backgroundColor = UIColor(red:1.0, green:0.0, blue:0.0, alpha:1.0)
        sideBarUIView.translatesAutoresizingMaskIntoConstraints = false
        sideBarUIView.isUserInteractionEnabled = true
        return sideBarUIView
    }()

    let mainView: UIView = {
        let mainView = UIView()
        mainView.backgroundColor = UIColor(red:0.0, green:1.0, blue:0.0, alpha:1.0)
        mainView.translatesAutoresizingMaskIntoConstraints = false
        mainView.isUserInteractionEnabled = true
        return mainView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        let settingsButton = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(HandleSettings))
        navigationItem.leftBarButtonItem = settingsButton
        view.backgroundColor = UIColor.white

        view.addSubview(mainView)
        view.addSubview(sideBarUIView)

        let SideBarPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
        let MainViewPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
        mainView.addGestureRecognizer(MainViewPanGestureRecognizer)
        sideBarUIView.addGestureRecognizer(SideBarPanGestureRecognizer)

        sideBarSwipeLeftOffset = 80
        siderBarSwipeRight = sideBarUIView.center
        sideBarSwipeLeft = CGPoint(x: sideBarUIView.center.x + sideBarSwipeLeftOffset, y: sideBarUIView.center.y)

        setupLayout()
    }


    @IBAction func didPan(sender: UIPanGestureRecognizer) {
        let velocity = sender.velocity(in: view)
        let translation = sender.translation(in: view)

        if sender.state == .began {
            trayOriginalCenter = sideBarUIView.center
        } else if sender.state == .changed {
            print("Gesture began")
            sideBarUIView.center = CGPoint(x: trayOriginalCenter.x + translation.x, y: trayOriginalCenter.y)
        } else if sender.state == .ended {
            print("Gesture ended")
            if velocity.x > 0 {
                UIView.animate(withDuration: 0.3) {
                    self.sideBarUIView.center = self.sideBarSwipeLeft
                }
            } else {
                UIView.animate(withDuration: 0.3) {
                    self.sideBarUIView.center = self.siderBarSwipeRight
                }
            }

        }
    }

    @IBAction func HandleSettings(sender : UIButton) {
        print ("Show settings")
    }

    private func setupLayout(){

        // CONSTRAINTS

        mainView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
        mainView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true

        sideBarUIView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
        sideBarUIView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
        sideBarUIView.rightAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
    }

}

Solution

  • So I figured out the problem. In the sender.state == .ended, I was using self.sideBarUIView.center = self.sideBarSwipeLeft which kept pushing the view up to the center of the screen.

    Edit:

    I decided to make the hamburger menu from scratch. Taking some ideas from this example: https://github.com/iosapptemplates/drawer-menu-swift

    Then I finally went and added some nice touches like if the menu isn't swiped over enough it was hidden away again.

    Here is my final code if anyone needs it.

    class hamburgerMenu: UIViewController, UIGestureRecognizerDelegate {
    
        let screenHeight = UIScreen.main.bounds.height
        let screenWidth = UIScreen.main.bounds.width
        var sideBarOriginalCenter: CGPoint!
    
        var mainView: UIView! = {
            let mainView = UIView()
            mainView.backgroundColor = UIColor(red:0.0, green:1.0, blue:0.0, alpha:1.0)
            mainView.translatesAutoresizingMaskIntoConstraints = false
            return mainView
        }()
        var overlayView: UIView! = {
            let viewBlack = UIView()
            viewBlack.backgroundColor = UIColor(red:0.0, green:0.0, blue:0.0, alpha:1.0)
            viewBlack.translatesAutoresizingMaskIntoConstraints = false
            return viewBlack
        }()
        var sideBarUIView: UIView! = {
            let sideBarUIView = UIView()
            sideBarUIView.backgroundColor = UIColor(red:1.0, green:0.0, blue:0.0, alpha:1.0)
            sideBarUIView.translatesAutoresizingMaskIntoConstraints = false
            return sideBarUIView
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let settingsButton = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(HandleSettings))
            navigationItem.leftBarButtonItem = settingsButton
            view.backgroundColor = UIColor.white
    
            view.addSubview(mainView)
            view.addSubview(overlayView)
            view.addSubview(sideBarUIView)
    
            overlayView.alpha = 0
    
            let swipeLeftGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
            mainView.addGestureRecognizer(swipeLeftGesture)
    
            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapOverlay))
            mainView.addGestureRecognizer(tapGesture)
    
            setupLayout()
    
        }
    
        @IBAction func didPan(sender: UIPanGestureRecognizer) {
            let translation = sender.translation(in: view)
    
            if sender.state == .began {
                sideBarOriginalCenter = sideBarUIView.center
            } else if sender.state == .changed {
                sideBarUIView.center = CGPoint(x: sideBarOriginalCenter.x + translation.x, y: sideBarUIView.center.y)
            } else if sender.state == .ended {
                let negHalfScreenWidth = ((self.screenWidth/2) * -1) / 2 // This should make -187.5 on iphoneX
    
                if sideBarUIView.center.x > negHalfScreenWidth {
                    UIView.animate(withDuration: 0.3) {
    
                        if self.sideBarUIView.center.x > negHalfScreenWidth {
                            let leftSideOfScreen = self.screenWidth - self.screenWidth
                            self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
                        }
                    }
                } else {
                    UIView.animate(withDuration: 0.3) {
                        let leftSideOfScreen = (self.screenWidth / 2) * -1
                        self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
                    }
                }
            }
        }
    
        @IBAction fileprivate func didTapOverlay() {
            UIView.animate(withDuration: 0.3, animations: {
                let leftSideOfScreen = (self.screenWidth / 2) * -1
                self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
            }) { (success) in
            }
        }
    
        @IBAction func HandleSettings(sender : UIButton) {
            if (sideBarUIView.center.x == 0) {
                UIView.animate(withDuration: 0.3, animations: {
                    let leftSideOfScreen = (self.screenWidth / 2) * -1
                    self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
                })
            } else if (sideBarUIView.center.x < -0.1) {
                UIView.animate(withDuration: 0.3, animations: {
                    let leftSideOfScreen = self.screenWidth - self.screenWidth
                    self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
                })
            }
        }
    
        private func setupLayout(){
            mainView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
            mainView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
    
            sideBarUIView.rightAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
            sideBarUIView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
            sideBarUIView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
            sideBarUIView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
    
            overlayView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
            overlayView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
        }
    
    }