Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to closed when in tap outside ? #75

Open
smart-technology opened this issue Jun 29, 2015 · 16 comments
Open

How to closed when in tap outside ? #75

smart-technology opened this issue Jun 29, 2015 · 16 comments

Comments

@smart-technology
Copy link

hi how i can close menu when in tap outside menu ?

@evnaz
Copy link
Owner

evnaz commented Jul 3, 2015

At the moment there is no such possibility.
To hide the side menu you can call hideSideMenuView() function in any view controller.

@elsesiy
Copy link
Contributor

elsesiy commented Jul 4, 2015

@smart-technology Shouldn't be too difficult to implement this feature yourself. Just register a tap recognizer in your view controllers which triggers hideSideMenuView(), or am I missing something?

@pappzsolt100
Copy link

My solution for this. I've added the following code in ENSideMenu.swift main init:
let outterView = UIView(frame: CGRectMake(0, 0, sourceView.bounds.width, sourceView.bounds.height))
outterView.backgroundColor = UIColor.clearColor()
let tapRecognizer = UITapGestureRecognizer(target: self, action: "hideSideMenu")
outterView.addGestureRecognizer(tapRecognizer)
sideMenuContainerView.addSubview(outterView)

and adjust the menu width and animation like this:

public convenience init(sourceView: UIView, menuViewController: UIViewController, menuPosition: ENSideMenuPosition) {
...
let menuFrame = CGRectMake(0, 0, sideMenuContainerView.bounds.width-30, sideMenuContainerView.bounds.height)
self.menuViewController.view.frame = menuFrame
...
}

func updateFrame() {
...
isMenuOpen ? 0 : -sourceView.frame.size.width-0.0 :
...
}

private func toggleMenu (shouldOpen: Bool) {
...
destFrame = CGRectMake((shouldOpen) ? -2.0 : -sourceView.frame.size.width, 0, sourceView.frame.size.width, height)
...
}

I hope its help!

@PankovSerge
Copy link

@pappzsolt100 your solution is not perfect and so complex.

Quick workaround, but it's not clear too:

        outterView = UIView(frame: CGRectMake(sideMenuContainerView.frame.width, 0,
            sourceView.frame.width - sideMenuContainerView.frame.width,
            sourceView.frame.height))
        outterView.backgroundColor = UIColor.clearColor()
        let tapRecognizer = UITapGestureRecognizer(target: self, action: "hideSideMenu")
        outterView.addGestureRecognizer(tapRecognizer)
        outterView.userInteractionEnabled = false
        sourceView.addSubview(outterView)

and change user interaction of outerView in

private func toggleMenu (shouldOpen: Bool) {
        outterView.userInteractionEnabled = shouldOpen

it's all !
Have a nice day!

@mortenholmgaard
Copy link

Works great!

@lakhshya
Copy link

@PankovSerge it works fine. Thanks.

@jackandjill
Copy link

@PankovSerge where does your code go in to?

@jackandjill
Copy link

@lakhshya Where did you put this code?

@lakhshya
Copy link

Define outterView in ENSideMenu
private var outterView: UIView = UIView()

Add @PankovSerge 's code in the public init of ENSideMenu

@jackandjill
Copy link

@lakhshya WORKS PERFECT! thankyou.

@DagarAmit
Copy link

i can't click on item over outterView. I use .removeView method, but its work only one time.
Please give me suggetion

@DagarAmit
Copy link

Thank you..its WORK!!!

@zishanj
Copy link

zishanj commented Apr 8, 2016

The solution of @PankovSerge works perfectly. Just in case if you have modified the width of ENSideMenu like sideMenu?.menuWidth = 220, then you have to put

outterView.frame = CGRectMake(sideMenuContainerView.frame.width, 0, sourceView.frame.width - sideMenuContainerView.frame.width,
            sourceView.frame.height)

again in the end of updateFrame() function

@ghost
Copy link

ghost commented May 2, 2016

Thanks @zishanj !! 👍

@djdance
Copy link

djdance commented Jan 23, 2017

Swift 3.0 with animated dimming

        //beefore sideMenuContainerView!
        outterView = UIView(frame: CGRect(x:0, y:0,width:sourceView.frame.width,height:sourceView.frame.height))
        outterView.backgroundColor = UIColor.black
        //outterView.layer.opacity=0.2
        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ENSideMenu.hideSideMenu))
        outterView.addGestureRecognizer(tapRecognizer)
        outterView.isUserInteractionEnabled = false
        outterView.isHidden = true
        outterView.alpha=0
        sourceView.addSubview(outterView)

and in toggleMenu

// Fade in/out
        if shouldOpen {
            outterView.isUserInteractionEnabled = shouldOpen
            outterView.isHidden = !shouldOpen
            UIView.animate(withDuration: 0.4, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {self.outterView.alpha = 0.5}, completion: nil)
        } else  {
            UIView.animate(withDuration: 0.4, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {self.outterView.alpha = 0.0}, completion: {Void in
                self.outterView.isUserInteractionEnabled = shouldOpen
                self.outterView.isHidden = !shouldOpen
            })
        }

@kamal0289
Copy link

Where I put this code in this class pls :

import UIKit

public protocol ENSideMenuDelegate: class {
func sideMenuWillOpen()
func sideMenuWillClose()
func sideMenuShouldOpenSideMenu () -> Bool
func sideMenuDidOpen()
func sideMenuDidClose()
}

public protocol ENSideMenuProtocol: class {
// var sideMenu : ENSideMenu? { get }
// func setContentViewController(_ contentViewController: UIViewController)
var sideMenu : ENSideMenu? { get }
func setContentViewController(contentViewController: UIViewController, push:Bool)
}

public enum ENSideMenuAnimation : Int {
case none
case default
}
/**
The position of the side view on the screen.

  • Left: Left side of the screen
  • Right: Right side of the screen
    */
    public enum ENSideMenuPosition : Int {
    case left
    case right
    }

public extension UIViewController {
/**
Changes current state of side menu view.
/
public func toggleSideMenuView () {
sideMenuController()?.sideMenu?.toggleMenu()
}
/
*
Hides the side menu view.
/
public func hideSideMenuView () {
sideMenuController()?.sideMenu?.hideSideMenu()
}
/
*
Shows the side menu view.
*/
public func showSideMenuView () {
sideMenuController()?.sideMenu?.showSideMenu()
}

/**
Returns a Boolean value indicating whether the side menu is showed.

:returns: BOOL value
*/
public func isSideMenuOpen () -> Bool {
    let sieMenuOpen = sideMenuController()?.sideMenu?.isMenuOpen
    return sieMenuOpen!
}

/**
 * You must call this method from viewDidLayoutSubviews in your content view controlers so it fixes size and position of the side menu when the screen
 * rotates.
 * A convenient way to do it might be creating a subclass of UIViewController that does precisely that and then subclassing your view controllers from it.
 */
func fixSideMenuSize() {
    if let navController = navigationController as? ENSideMenuNavigationController {
        navController.sideMenu?.updateFrame()
    }
}
/**
Returns a view controller containing a side menu

:returns: A `UIViewController`responding to `ENSideMenuProtocol` protocol
*/
public func sideMenuController () -> ENSideMenuProtocol? {
    var iteration : UIViewController? = parent
    if (iteration == nil) {
        return topMostController()
    }
    repeat {
        if (iteration is ENSideMenuProtocol) {
            return iteration as? ENSideMenuProtocol
        } else if (iteration?.parent != nil && iteration?.parent != iteration) {
            iteration = iteration!.parent
        } else {
            iteration = nil
        }
    } while (iteration != nil)

    return iteration as? ENSideMenuProtocol
}

internal func topMostController () -> ENSideMenuProtocol? {
    var topController : UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    if (topController is UITabBarController) {
        topController = (topController as! UITabBarController).selectedViewController
    }
    var lastMenuProtocol : ENSideMenuProtocol?
    while (topController?.presentedViewController != nil) {
        if(topController?.presentedViewController is ENSideMenuProtocol) {
            lastMenuProtocol = topController?.presentedViewController as? ENSideMenuProtocol
        }
        topController = topController?.presentedViewController
    }

    if (lastMenuProtocol != nil) {
        return lastMenuProtocol
    }
    else {
        return topController as? ENSideMenuProtocol
    }
}

}

open class ENSideMenu : NSObject, UIGestureRecognizerDelegate {
/// The width of the side menu view. The default value is 160.
open var menuWidth : CGFloat = 160.0 {
didSet {
needUpdateApperance = true
updateSideMenuApperanceIfNeeded()
updateFrame()
}
}
fileprivate var menuPosition:ENSideMenuPosition = .left
fileprivate var blurStyle: UIBlurEffectStyle = .light
/// A Boolean value indicating whether the bouncing effect is enabled. The default value is TRUE.
open var bouncingEnabled :Bool = true
/// The duration of the slide animation. Used only when bouncingEnabled is FALSE.
open var animationDuration = 0.4
fileprivate let sideMenuContainerView = UIView()
fileprivate(set) var sidemenuViewController : UIViewController!
fileprivate var animator : UIDynamicAnimator!
fileprivate var sourceView : UIView!
fileprivate var needUpdateApperance : Bool = false
/// The delegate of the side menu
open weak var delegate : ENSideMenuDelegate?
fileprivate(set) var isMenuOpen : Bool = false
/// A Boolean value indicating whether the left swipe is enabled.
open var allowLeftSwipe : Bool = true
/// A Boolean value indicating whether the right swipe is enabled.
open var allowRightSwipe : Bool = true
open var allowPanGesture : Bool = true
fileprivate var panRecognizer : UIPanGestureRecognizer?

/**
Initializes an instance of a `ENSideMenu` object.

:param: sourceView   The parent view of the side menu view.
:param: menuPosition The position of the side menu view.

:returns: An initialized `ENSideMenu` object, added to the specified view.
*/
public init(sourceView: UIView, menuPosition: ENSideMenuPosition, blurStyle: UIBlurEffectStyle = .light) {
    super.init()
    self.sourceView = sourceView
    self.menuPosition = menuPosition
    self.blurStyle = blurStyle
    self.setupMenuView()

    animator = UIDynamicAnimator(referenceView:sourceView)
    animator.delegate = self

    self.panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ENSideMenu.handlePan(_:)))
    panRecognizer!.delegate = self
    sourceView.addGestureRecognizer(panRecognizer!)

    // Add right swipe gesture recognizer
    let rightSwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(ENSideMenu.handleGesture(_:)))
    rightSwipeGestureRecognizer.delegate = self
    rightSwipeGestureRecognizer.direction =  UISwipeGestureRecognizerDirection.right

    // Add left swipe gesture recognizer
    let leftSwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(ENSideMenu.handleGesture(_:)))
    leftSwipeGestureRecognizer.delegate = self
    leftSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.left

    if (menuPosition == .left) {
        sourceView.addGestureRecognizer(rightSwipeGestureRecognizer)
        sideMenuContainerView.addGestureRecognizer(leftSwipeGestureRecognizer)
    }
    else {
        sideMenuContainerView.addGestureRecognizer(rightSwipeGestureRecognizer)
        sourceView.addGestureRecognizer(leftSwipeGestureRecognizer)
    }

}
/**
Initializes an instance of a `ENSideMenu` object.

:param: sourceView         The parent view of the side menu view.
:param: sidemenuViewController A menu view controller object which will be placed in the side menu view.
:param: menuPosition       The position of the side menu view.

:returns: An initialized `ENSideMenu` object, added to the specified view, containing the specified menu view controller.
*/
public convenience init(sourceView: UIView, sidemenuViewController: UIViewController, menuPosition: ENSideMenuPosition, blurStyle: UIBlurEffectStyle = .light) {
    self.init(sourceView: sourceView, menuPosition: menuPosition, blurStyle: blurStyle)
    self.sidemenuViewController = sidemenuViewController
    sidemenuViewController.view.frame = sideMenuContainerView.bounds
    sidemenuViewController.view.autoresizingMask =  [.flexibleHeight, .flexibleWidth]
    sideMenuContainerView.addSubview(sidemenuViewController.view)
}
/**
Updates the frame of the side menu view.
*/
func updateFrame() {
    var width:CGFloat
    var height:CGFloat
    (width, height) = adjustFrameDimensions( sourceView.frame.size.width, height: sourceView.frame.size.height)
    let menuFrame = CGRect(
        x: (menuPosition == .left) ?
            isMenuOpen ? 0 : -menuWidth-1.0 :
            isMenuOpen ? width - menuWidth : width+1.0,
        y: sourceView.frame.origin.y,
        width: menuWidth,
        height: height
    )
    sideMenuContainerView.frame = menuFrame
}

fileprivate func adjustFrameDimensions( _ width: CGFloat, height: CGFloat ) -> (CGFloat,CGFloat) {
    if floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1 &&
        (UIApplication.shared.statusBarOrientation == UIInterfaceOrientation.landscapeRight ||
            UIApplication.shared.statusBarOrientation == UIInterfaceOrientation.landscapeLeft) {
                // iOS 7.1 or lower and landscape mode -> interchange width and height
                return (height, width)
    }
    else {
        return (width, height)
    }

}

fileprivate func setupMenuView() {

    
    // Configure side menu container
    updateFrame()

    sideMenuContainerView.backgroundColor = UIColor.clear
    sideMenuContainerView.clipsToBounds = false
    sideMenuContainerView.layer.masksToBounds = false
    sideMenuContainerView.layer.shadowOffset = (menuPosition == .left) ? CGSize(width: 1.0, height: 1.0) : CGSize(width: -1.0, height: -1.0)
    sideMenuContainerView.layer.shadowRadius = 1.0
    sideMenuContainerView.layer.shadowOpacity = 0.125
    sideMenuContainerView.layer.shadowPath = UIBezierPath(rect: sideMenuContainerView.bounds).cgPath

    sourceView.addSubview(sideMenuContainerView)

    if (NSClassFromString("UIVisualEffectView") != nil) {
        // Add blur view
        let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) as UIVisualEffectView
        visualEffectView.frame = sideMenuContainerView.bounds
        visualEffectView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        sideMenuContainerView.addSubview(visualEffectView)
    }
    else {
        // TODO: add blur for ios 7
    }
}

fileprivate func toggleMenu (_ shouldOpen: Bool) {
    if shouldOpen, delegate?.sideMenuShouldOpenSideMenu() == false {
        return
    }
    updateSideMenuApperanceIfNeeded()
    isMenuOpen = shouldOpen
    var width:CGFloat
    var height:CGFloat
    (width, height) = adjustFrameDimensions( sourceView.frame.size.width, height: sourceView.frame.size.height)
    if (bouncingEnabled) {

        animator.removeAllBehaviors()

        var gravityDirectionX: CGFloat
        var pushMagnitude: CGFloat
        var boundaryPointX: CGFloat
        var boundaryPointY: CGFloat

        if (menuPosition == .left) {
            // Left side menu
            gravityDirectionX = (shouldOpen) ? 1 : -1
            pushMagnitude = (shouldOpen) ? 35 : -35
            boundaryPointX = (shouldOpen) ? menuWidth : -menuWidth-2
            boundaryPointY = 25
        }
        else {
            // Right side menu
            gravityDirectionX = (shouldOpen) ? -1 : 1
            pushMagnitude = (shouldOpen) ? -35 : 35
            boundaryPointX = (shouldOpen) ? width-menuWidth : width+menuWidth+2
            boundaryPointY =  -25
        }

        let gravityBehavior = UIGravityBehavior(items: [sideMenuContainerView])
        gravityBehavior.gravityDirection = CGVector(dx: gravityDirectionX,  dy: 0)
        animator.addBehavior(gravityBehavior)

        let collisionBehavior = UICollisionBehavior(items: [sideMenuContainerView])
        collisionBehavior.addBoundary(withIdentifier: "menuBoundary" as NSCopying, from: CGPoint(x: boundaryPointX, y: boundaryPointY),
            to: CGPoint(x: boundaryPointX, y: height))
        animator.addBehavior(collisionBehavior)

        let pushBehavior = UIPushBehavior(items: [sideMenuContainerView], mode: UIPushBehaviorMode.instantaneous)
        pushBehavior.magnitude = pushMagnitude
        animator.addBehavior(pushBehavior)

        let menuViewBehavior = UIDynamicItemBehavior(items: [sideMenuContainerView])
        menuViewBehavior.elasticity = 0.25
        animator.addBehavior(menuViewBehavior)

    }
    else {
        var destFrame :CGRect
        if (menuPosition == .left) {
            destFrame = CGRect(x: (shouldOpen) ? -2.0 : -menuWidth-2, y: 0, width: menuWidth, height: height)
        }
        else {
            destFrame = CGRect(x: (shouldOpen) ? width-menuWidth : width+2.0,
                                    y: 0,
                                    width: menuWidth,
                                    height: height)
        }

        UIView.animate(
            withDuration: animationDuration,
            animations: { [weak self] () -> Void in
                self?.sideMenuContainerView.frame = destFrame
            },
            completion: { [weak self] (Bool) -> Void in
                guard let strongSelf = self else { return }
                if (strongSelf.isMenuOpen) {
                    strongSelf.delegate?.sideMenuDidOpen()
                } else {
                    strongSelf.delegate?.sideMenuDidClose()
                }
        })
    }

    if (shouldOpen) {
        delegate?.sideMenuWillOpen()
    } else {
        delegate?.sideMenuWillClose()
    }
}

open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {

    if delegate?.sideMenuShouldOpenSideMenu() == false {
        return false
    }

    if gestureRecognizer is UISwipeGestureRecognizer {
        let swipeGestureRecognizer = gestureRecognizer as! UISwipeGestureRecognizer
        if !allowLeftSwipe {
            if swipeGestureRecognizer.direction == .left {
                return false
            }
        }

        if !allowRightSwipe {
            if swipeGestureRecognizer.direction == .right {
                return false
            }
        }
    }
    else if gestureRecognizer.isEqual(panRecognizer) {
        if allowPanGesture == false {
            return false
        }
        animator.removeAllBehaviors()
        let touchPosition = gestureRecognizer.location(ofTouch: 0, in: sourceView)
        if menuPosition == .left {
            if isMenuOpen {
                if touchPosition.x < menuWidth {
                    return true
                }
            }
            else {
                if touchPosition.x < 25 {
                    return true
                }
            }
        }
        else {
            if isMenuOpen {
                if touchPosition.x > sourceView.frame.width - menuWidth {
                    return true
                }
            }
            else {
                if touchPosition.x > sourceView.frame.width-25 {
                    return true
                }
            }
        }

        return false
    }
    return true
}

@objc internal func handleGesture(_ gesture: UISwipeGestureRecognizer) {
    toggleMenu((menuPosition == .right && gesture.direction == .left)
            || (menuPosition == .left && gesture.direction == .right))
}

@objc internal func handlePan(_ recognizer : UIPanGestureRecognizer){

    let leftToRight = recognizer.velocity(in: recognizer.view).x > 0

    switch recognizer.state {
    case .began:

        break

    case .changed:

        let translation = recognizer.translation(in: sourceView).x
        let xPoint : CGFloat = sideMenuContainerView.center.x + translation + (menuPosition == .left ? 1 : -1) * menuWidth / 2

        if menuPosition == .left {
            if xPoint <= 0 || xPoint > sideMenuContainerView.frame.width {
                return
            }
        }else{
            if xPoint <= sourceView.frame.size.width - menuWidth || xPoint >= sourceView.frame.size.width
            {
                return
            }
        }

        sideMenuContainerView.center.x = sideMenuContainerView.center.x + translation
        recognizer.setTranslation(CGPoint.zero, in: sourceView)

    default:

        let shouldClose = menuPosition == .left ? !leftToRight && sideMenuContainerView.frame.maxX < menuWidth : leftToRight && sideMenuContainerView.frame.minX >  (sourceView.frame.size.width - menuWidth)

        toggleMenu(!shouldClose)

    }
}

fileprivate func updateSideMenuApperanceIfNeeded () {
    if (needUpdateApperance) {
        var frame = sideMenuContainerView.frame
        frame.size.width = menuWidth
        sideMenuContainerView.frame = frame
        sideMenuContainerView.layer.shadowPath = UIBezierPath(rect: sideMenuContainerView.bounds).cgPath

        needUpdateApperance = false
    }
}

/**
Toggles the state of the side menu.
*/
open func toggleMenu () {
    if (isMenuOpen) {
        toggleMenu(false)
    }
    else {
        updateSideMenuApperanceIfNeeded()
        toggleMenu(true)
    }
}
/**
Shows the side menu if the menu is hidden.
*/
open func showSideMenu () {
    if (!isMenuOpen) {
        toggleMenu(true)
    }
}
/**
Hides the side menu if the menu is showed.
*/
open func hideSideMenu () {
    if (isMenuOpen) {
        toggleMenu(false)
    }
}

}

extension ENSideMenu: UIDynamicAnimatorDelegate {
public func dynamicAnimatorDidPause(_ animator: UIDynamicAnimator) {
if (isMenuOpen) {
delegate?.sideMenuDidOpen()
} else {
delegate?.sideMenuDidClose()
}
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests