Shahid Miah
Shahid Miah

Reputation: 31

Dragging UIView

I need some help with dragging UIView to reveal, menu view underneath main view.

I have two UIViews. menuView - includes menu buttons and labels and mainView - located over menuView.

I want to drag the main view from left edge to show menu items and snap the main view to a specific position. I am able to get the dragging to the right working but I cannot set it back to original position when dragging left.

here is my codes I have been playing around with but no success. I also wanted to make mainView smaller as I drag it to right. any help will be greatly appreciated.

Note: PanGesture is attached to mainView.

@IBAction func dragMenu(_ sender: UIPanGestureRecognizer) {
    let mview = sender.view!
    let originalCenter = CGPoint(x: self.mainView.bounds.width/2, y: self.mainView.bounds.height/2)
    switch sender.state {

    case .changed:
        if let mview = sender.view {
            mview.center.x = mview.center.x + sender.translation(in: view).x
            sender.setTranslation(CGPoint.zero, in: view)
        }

    case .ended:


            let DraggedHalfWayRight = mview.center.x > view.center.x
            if DraggedHalfWayRight {
                //dragginToRight

                showMenu = !showMenu
                self.mainViewRight.constant = 200
                                    self.mainTop.constant = 50
                                    self.mainBottom.constant = 50
                                    self.mainLeft.constant = 200

            } else //dragging left and set it back to original position.
            {
                mview.center = originalCenter
                 showMenu = !showMenu
        }



    default:
        break
    }
}

Upvotes: 0

Views: 1574

Answers (1)

Rob
Rob

Reputation: 437622

I'd suggest a few things:

  1. When you're done dragging the menu off, make sure to set its alpha to zero (so that, if you were in portrait and go to landscape, you don't suddenly see the menu sitting there).

  2. I personally just adjust the transform of the dragged view and avoid resetting the translation of the gesture back to zero all the time. That's a matter of personal preference.

  3. I'd make the view controller the delegate for the gesture and implement gestureRecognizerShouldBegin (because if the menu is hidden, you don't want to recognize swipes to try to hide it again; and if it's not hidden, you don't want to recognize swipes to show it).

  4. When figuring out whether to complete the gesture or not, I also consider the velocity of the gesture (e.g. so a little flick will dismiss/show the animated view).

Thus:

class ViewController: UIViewController {

    @IBOutlet weak var menuView: UIView!

    var isMenuVisible = true

    @IBAction func dragMenu(_ gesture: UIPanGestureRecognizer) {
        let translationX = gesture.translation(in: gesture.view!).x

        switch gesture.state {
        case .began:
            // if the menu is not visible, make sure it's off screen and then make it visible

            if !isMenuVisible {
                menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
                menuView.alpha = 1
            }
            fallthrough

        case .changed:
            if isMenuVisible {
                menuView.transform = CGAffineTransform(translationX: translationX, y: 0)
            } else {
                menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width + translationX, y: 0)
            }

        case .ended:
            let shouldComplete: Bool
            if isMenuVisible {
                shouldComplete = translationX > gesture.view!.bounds.width / 2 || gesture.velocity(in: gesture.view!).x > 0
            } else {
                shouldComplete = -translationX > gesture.view!.bounds.width / 2 || gesture.velocity(in: gesture.view!).x < 0
            }

            UIView.animate(withDuration: 0.25, animations: {
                if self.isMenuVisible && shouldComplete || !self.isMenuVisible && !shouldComplete {
                    self.menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
                } else {
                    self.menuView.transform = .identity
                }

                if shouldComplete{
                    self.isMenuVisible = !self.isMenuVisible
                }
            }, completion: { _ in
                self.menuView.alpha = self.isMenuVisible ? 1 : 0
            })

        default:
            break
        }
    }

}

extension ViewController: UIGestureRecognizerDelegate {

    func gestureRecognizerShouldBegin(_ gesture: UIGestureRecognizer) -> Bool {
        guard let gesture = gesture as? UIPanGestureRecognizer else { return true }

        let translationX = gesture.translation(in: gesture.view!).x

        return isMenuVisible && translationX > 0 || !isMenuVisible && translationX < 0
    }

}

Personally, I prefer to use a separate screen edge gesture recognizer to pull them menu back on screen (you don't really want it to recognize gestures anywhere, but just on that right edge). Another virtue of this approach is that by keeping "show" and "hide" in different functions, the code is a lot more readable (IMHO):

class ViewController: UIViewController {

    @IBOutlet weak var menuView: UIView!

    var isMenuVisible = true

    @IBAction func handleScreenEdgeGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
        let translationX = gesture.translation(in: gesture.view!).x

        switch gesture.state {
        case .began:
            menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
            menuView.alpha = 1
            fallthrough

        case .changed:
            menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width + translationX, y: 0)

        case .ended:
            let shouldComplete = -translationX > gesture.view!.bounds.width / 2 || gesture.velocity(in: gesture.view!).x < 0

            UIView.animate(withDuration: 0.25, delay:0, options: .curveEaseOut, animations: {
                if shouldComplete {
                    self.menuView.transform = .identity
                    self.isMenuVisible = !self.isMenuVisible
                } else {
                    self.menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
                }
            }, completion: { _ in
                self.menuView.alpha = self.isMenuVisible ? 1 : 0
            })

        default:
            break
        }
    }

    @IBAction func dragMenu(_ gesture: UIPanGestureRecognizer) {
        let translationX = gesture.translation(in: gesture.view!).x

        switch gesture.state {
        case .began, .changed:
            menuView.transform = CGAffineTransform(translationX: translationX, y: 0)

        case .ended:
            let shouldComplete = translationX > gesture.view!.bounds.width / 2 || gesture.velocity(in: gesture.view!).x > 0

            UIView.animate(withDuration: 0.25, delay:0, options: .curveEaseOut, animations: {
                if shouldComplete {
                    self.menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
                    self.isMenuVisible = !self.isMenuVisible
                } else {
                    self.menuView.transform = .identity
                }
            }, completion: { _ in
                self.menuView.alpha = self.isMenuVisible ? 1 : 0
            })

        default:
            break
        }
    }

}

extension ViewController: UIGestureRecognizerDelegate {

    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer is UIScreenEdgePanGestureRecognizer {
            return !isMenuVisible
        } else if let gesture = gestureRecognizer as? UIPanGestureRecognizer {
            let translationX = gesture.translation(in: gesture.view!).x
            return isMenuVisible && translationX > 0
        }

        return true
    }

}

Upvotes: 1

Related Questions