evnaz / ENSwiftSideMenu

A simple side menu for iOS written in Swift.
MIT License
1.82k stars 281 forks source link

How to closed when in tap outside ? #75

Open smart-technology opened 9 years ago

smart-technology commented 9 years ago

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

evnaz commented 9 years ago

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

elsesiy commented 9 years ago

@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 commented 9 years ago

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 commented 9 years ago

@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 commented 8 years ago

Works great!

lakhshya commented 8 years ago

@PankovSerge it works fine. Thanks.

jackandjill commented 8 years ago

@PankovSerge where does your code go in to?

jackandjill commented 8 years ago

@lakhshya Where did you put this code?

lakhshya commented 8 years ago

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

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

jackandjill commented 8 years ago

@lakhshya WORKS PERFECT! thankyou.

DagarAmit commented 8 years ago

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

DagarAmit commented 8 years ago

Thank you..its WORK!!!

zishanj commented 8 years ago

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 commented 8 years ago

Thanks @zishanj !! 👍

djdance commented 7 years ago

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 commented 6 years ago

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.

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