I have the function below. It works properly.
When a user types any character it validates the user input and hides some imageView based on the input.
#IBAction func onEmailValueChanged(_ sender: UITextField) {
let hasMinimumLength = TextValidationHelper.validateHasMinimumLength(password: sender.text!)
passLengthCheckmarkImageView.isHidden = hasMinimumLength ? false : true
let hasCapitalLetter = TextValidationHelper.validateHasCapitalLetter(password: sender.text!)
passHasUppercaseCheckmarkImageView.isHidden = hasCapitalLetter ? false : true
let hasNumber = TextValidationHelper.validateHasNumber(password: sender.text!)
passHasNumberCheckmarkImageView.isHidden = hasNumber ? false : true
let hasSpecialCharacter = TextValidationHelper.validateHasSpecialCharacter(password: sender.text!)
passHasSymbolCheckmarkImageView.isHidden = hasSpecialCharacter ? false : true
resetButton.isHidden = hasMinimumLength && hasCapitalLetter && hasNumber && hasSpecialCharacter ? false : true
}
But now I want to apply an MVP model on this function to remove the function from the ViewController file.
How can I do that?
Do I need to publish more code to make it possible to create an answer for this question?
It is not a good practice to use any architectural pattern only for method. So assuming you are having a complete app with many classes or files.
An important thing is that it is not fixed/compulsory to use any specific pattern. It actually depends on the code, sometimes you end up writing much code just to handle a method. So try to think the optimal approach to make the code more testable and scalable.
But for your reference, you can check the following code:
On ViewController:
lazy var presenter:Presenter = Presenter(view:self)
#IBAction func onEmailValueChanged(_ sender: UITextField) {
presenter.validateHasMinimumLength(password: sender.text!)
presenter.validateHasCapitalLetter(password: sender.text!)
presenter.validateHasNumber(password: sender.text!)
presenter.validateHasSpecialCharacter(password: sender.text!)
}
//Adopting ViewController:PrensenterViewProtocol on ViewController
extension ViewController:PrensenterViewProtocol {
func updateLengthCheckmarkImageView(isHidden:Bool) {
passLengthCheckmarkImageView.isHidden = isHidden
}
func updateUpperCaseCheckmarkImageView(isHidden:Bool) {
passHasUppercaseCheckmarkImageView.isHidden = isHidden
}
func updateNumberCheckmarkImageView(isHidden:Bool) {
passHasNumberCheckmarkImageView.isHidden = isHidden
}
func updateSymbolCheckmarkImageView(isHidden:Bool) {
passHasSymbolCheckmarkImageView.isHidden = isHidden
}
func updateResetButton(isHidden:Bool) {
resetButton.isHidden = isHidden
}
}
PresenterView protocol as:
protocol PrensenterViewProtocol:NSObjectProtocol {
func updateLengthCheckmarkImageView(isHidden:Bool)
func updateUpperCaseCheckmarkImageView(isHidden:Bool)
func updateNumberCheckmarkImageView(isHidden:Bool)
func updateSymbolCheckmarkImageView(isHidden:Bool)
func updateResetButton(isHidden:Bool)
}
Presenter as:
class Presenter {
weak var view:PrensenterViewProtocol!
private var hasMinimumLength:Bool = false
private var hasCapitalLetter:Bool = false
private var hasNumber:Bool = false
private var hasSpecialCharacter:Bool = false
init(view:PrensenterViewProtocol) {
self.view = view
}
func validateHasMinimumLength(password:String?) {
hasMinimumLength = TextValidationHelper.validateHasMinimumLength(password: password)
self.view.updateLengthCheckmarkImageView(isHidden: hasMinimumLength)
checkAllValidations()
}
func validateHasCapitalLetter(password:String?) {
hasCapitalLetter = TextValidationHelper.validateHasCapitalLetter(password: password)
self.view.updateUpperCaseCheckmarkImageView(isHidden:hasCapitalLetter )
checkAllValidations()
}
func validateHasNumber(password:String?) {
hasNumber = TextValidationHelper.validateHasNumber(password: password)
self.view.updateNumberCheckmarkImageView(isHidden: hasNumber)
checkAllValidations()
}
func validateHasSpecialCharacter(password:String?) {
hasSpecialCharacter = TextValidationHelper.validateHasSpecialCharacter(password: password)
self.view.updateSymbolCheckmarkImageView(isHidden: hasSpecialCharacter)
checkAllValidations()
}
func checkAllValidations() {
let areAllValid:Bool = hasMinimumLength && hasCapitalLetter && hasNumber && hasSpecialCharacter ? false : true
self.view.updateResetButton(isHidden: areAllValid)
}
}
I am currently trying to implement the new iOS 14 UIColorPicker. Everything works great, except the eye dropper functionality of the UIColorPicker. After selecting the the eye dropper and sampling a color, the default behavior should be to re-open the UIColorPicker with your selected color as the active one. For some reason, this does not happen. Here is my implementation, its pretty standard, so I'm not sure why the eyedropper isnt behaving as expected.
I have these functions that are passed as the selectors for when I have some UI elements pressed
#objc func pickColorSky(sender: UIControl){
presentColorPicker(tag: 1, sender: sender)
}
#objc func pickColorBackground(sender: UIControl){
presentColorPicker(tag: 2, sender: sender)
}
#objc func pickColorGround(sender: UIControl){
presentColorPicker(tag: 3, sender: sender)
}
Here is the function that presents the UIColorPickerView itself
#objc private func presentColorPicker(tag: Int, sender: UIControl){
let vc = UIColorPickerViewController()
vc.supportsAlpha = false
vc.delegate = self
vc.view.tag = tag
vc.modalPresentationStyle = .popover
vc.popoverPresentationController?.sourceView = sender
vc.popoverPresentationController?.sourceRect = sender.bounds
self.present(vc, animated: true)
}
And the delegate methods to handle the interacting with the color picker
extension myViewController: UIColorPickerViewControllerDelegate {
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
guard let options = editor?.apiView?.getRenderingOptions() else { return }
if viewController.view.tag == 1 {
let newColor = getMobileApiColor(color: viewController.selectedColor)
options.skyColor = newColor
skyRow.color.backgroundColor = newColor.uiColor
}
else if viewController.view.tag == 2 {
let newColor = getMobileApiColor(color: viewController.selectedColor)
options.backgroundColor = newColor
backgroundRow.color.backgroundColor = newColor.uiColor
}
else if viewController.view.tag == 3 {
let newColor = getMobileApiColor(color: viewController.selectedColor)
options.groundColor = newColor
groundRow.color.backgroundColor = newColor.uiColor
}
editor?.modelView?.setNeedsDisplay()
}
Try this method colorPickerViewControllerDidSelectColor
extension ViewController: UIColorPickerViewControllerDelegate {
// Called once you have finished picking the color.
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
self.view.backgroundColor = viewController.selectedColor
}
// Called on every color selection done in the picker.
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
self.view.backgroundColor = viewController.selectedColor
}
}
You need to hold on to the picker object as a class member like this:
private lazy var colorPicker = makeColorPicker()
private func makeColorPicker() -> UIColorPickerViewController {
let vc = UIColorPickerViewController()
vc.delegate = self
vc.supportsAlpha = false
return vc
}
Otherwise, it is removed from memory before you complete the eye-dropper step.
I am currently working on an animation project for an Apple Development class where we are moving and animating an ImageView. I can move the image, but I'm stumped on how to return it to the origin location, preferably using the same button.
Code for the move animation is as follows:
#IBAction func move(_ sender: Any) {
UIView.animate(withDuration: 1, animations: {self.imageView.frame.origin.y -= 200
}, completion: nil)
}
declare a variable that will hold the value of your y origin when the view loaded. and also declare a variable that will check if the imageview is currently animating or not.
var onLoadFrameYOrigin : CGFloat = 0.0
var isAnimating : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
onLoadFrameYOrigin = self.searchView.frame.origin.y
}
#IBAction func move(_ sender: Any) {
if !isAnimating {
self.isAnimating = true
UIView.animate(withDuration: 1.0, animations: {
if self.onLoadFrameYOrigin == self.imageView.frame.origin.y {
self.imageView.frame.origin.y -= 200
} else {
self.imageView.frame.origin.y = onLoadFrameYOrigin
}
}) { _ in
self.isAnimating = false
}
}
}
Note : if you have make outlet of constraint of imageView then please
update constraint value during animation not frame.
1) make variable first
var isBack : Bool = false
Now in your button action :
#IBAction func move(_ sender: Any) {
if isBack == false
{
isBack = true
UIView.animate(withDuration: 1, animations: {self.imageView.frame.origin.y -= 200
}, completion: nil)
}
else
{
isBack = false
UIView.animate(withDuration: 1, animations: {self.imageView.frame.origin.y += 200
}, completion: nil)
}
}
I'm using the Slide menu controller pod in my project and have a little problem. When there are controllers in navigation stack and I want to swipe left, the burger menu appears instead of popping the contorller. How could that be changed? Could someone please guide me?
So I know it's a pretty basic functionality that you get for free, but it's overridden here.
I tried switching off the pan gesture for the slide menu, but it powered off the whole mechanism.
SlideMenuOptions.panGesturesEnabled = false
I also found the handleLeftPanGesture(_ panGesture: UIPanGestureRecognizer) method, which looks as follows:
open func isTargetViewController() -> Bool {
// Function to determine the target ViewController
// Please to override it if necessary
guard let navController = navigationController else { return true }
return navController.viewControllers.isEmpty
}
#objc func handleLeftPanGesture(_ panGesture: UIPanGestureRecognizer) {
if !isTargetViewController() {
navigationController?.popViewController(animated: true)
}
if isRightOpen() {
return
}
switch panGesture.state {
case UIGestureRecognizerState.began:
if LeftPanState.lastState != .ended && LeftPanState.lastState != .cancelled && LeftPanState.lastState != .failed {
return
}
if isLeftHidden() {
self.delegate?.leftWillOpen?()
} else {
self.delegate?.leftWillClose?()
}
LeftPanState.frameAtStartOfPan = leftContainerView.frame
LeftPanState.startPointOfPan = panGesture.location(in: view)
LeftPanState.wasOpenAtStartOfPan = isLeftOpen()
LeftPanState.wasHiddenAtStartOfPan = isLeftHidden()
leftViewController?.beginAppearanceTransition(LeftPanState.wasHiddenAtStartOfPan, animated: true)
addShadowToView(leftContainerView)
setOpenWindowLevel()
case UIGestureRecognizerState.changed:
if LeftPanState.lastState != .began && LeftPanState.lastState != .changed {
return
}
let translation: CGPoint = panGesture.translation(in: panGesture.view!)
leftContainerView.frame = applyLeftTranslation(translation, toFrame: LeftPanState.frameAtStartOfPan)
applyLeftOpacity()
applyLeftContentViewScale()
case UIGestureRecognizerState.ended, UIGestureRecognizerState.cancelled:
if LeftPanState.lastState != .changed {
setCloseWindowLevel()
return
}
let velocity:CGPoint = panGesture.velocity(in: panGesture.view)
let panInfo: PanInfo = panLeftResultInfoForVelocity(velocity)
if panInfo.action == .open {
if !LeftPanState.wasHiddenAtStartOfPan {
leftViewController?.beginAppearanceTransition(true, animated: true)
}
openLeftWithVelocity(panInfo.velocity)
track(.leftFlickOpen)
} else {
if LeftPanState.wasHiddenAtStartOfPan {
leftViewController?.beginAppearanceTransition(false, animated: true)
}
closeLeftWithVelocity(panInfo.velocity)
setCloseWindowLevel()
track(.leftFlickClose)
}
case UIGestureRecognizerState.failed, UIGestureRecognizerState.possible:
break
}
LeftPanState.lastState = panGesture.state
}
And I'm trying to count the navigaion ViewControllers, but it didn't help.
Actaully I found the solution which might help someone.
You need to add this to ViewController and conform to UIGestureRecognizerDelegate protocol
override func viewDidAppear(_ animated: Bool) {
slideMenuController()?.removeLeftGestures()
navigationController?.interactivePopGestureRecognizer?.delegate = self
}
extension AdditionalInformationController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.isEqual(navigationController?.interactivePopGestureRecognizer) {
navigationController?.popViewController(animated: true)
}
return false
}
}
This is my actual view
I want that the sidemenu begins at the first grey line and not on the top of the view. I would be glad if I could get an answer how i can set the height of it but i am also happy if someone could suggest me a workaround like hiding it behind the searchbar and segmented control. Thank you in advance.
This is my ENSideMenu.swift implementation
import UIKit
#objc public protocol ENSideMenuDelegate {
optional func sideMenuWillOpen()
optional func sideMenuWillClose()
optional func sideMenuDidOpen()
optional func sideMenuDidClose()
optional func sideMenuShouldOpenSideMenu () -> Bool
}
#objc public protocol ENSideMenuProtocol {
var sideMenu : ENSideMenu? { get }
func setContentViewController(contentViewController: UIViewController)
}
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 = self.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 = self.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? = self.parentViewController
if (iteration == nil) {
return topMostController()
}
repeat {
if (iteration is ENSideMenuProtocol) {
return iteration as? ENSideMenuProtocol
} else if (iteration?.parentViewController != nil && iteration?.parentViewController != iteration) {
iteration = iteration!.parentViewController
} else {
iteration = nil
}
} while (iteration != nil)
return iteration as? ENSideMenuProtocol
}
internal func topMostController () -> ENSideMenuProtocol? {
var topController : UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController
if (topController is UITabBarController) {
topController = (topController as! UITabBarController).selectedViewController
}
while (topController?.presentedViewController is ENSideMenuProtocol) {
topController = topController?.presentedViewController
}
return topController as? ENSideMenuProtocol
}
}
public class ENSideMenu : NSObject, UIGestureRecognizerDelegate {
/// The width of the side menu view. The default value is 160.
public var menuWidth : CGFloat = 90.0 {
didSet {
needUpdateApperance = true
updateFrame()
}
}
private var menuPosition:ENSideMenuPosition = .Right
private var blurStyle: UIBlurEffectStyle = .Light
/// A Boolean value indicating whether the bouncing effect is enabled. The default value is TRUE.
public var bouncingEnabled :Bool = true
/// The duration of the slide animation. Used only when `bouncingEnabled` is FALSE.
public var animationDuration = 0.25
private let sideMenuContainerView = UIView()
private(set) var menuViewController : UIViewController!
private var animator : UIDynamicAnimator!
private var sourceView : UIView!
private var needUpdateApperance : Bool = false
/// The delegate of the side menu
public weak var delegate : ENSideMenuDelegate?
private(set) var isMenuOpen : Bool = false
/// A Boolean value indicating whether the left swipe is enabled.
public var allowLeftSwipe : Bool = true
/// A Boolean value indicating whether the right swipe is enabled.
public var allowRightSwipe : Bool = true
public var allowPanGesture : Bool = true
private 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: "handlePan:")
panRecognizer!.delegate = self
sourceView.addGestureRecognizer(panRecognizer!)
// Add right swipe gesture recognizer
let rightSwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "handleGesture:")
rightSwipeGestureRecognizer.delegate = self
rightSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right
// Add left swipe gesture recognizer
let leftSwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "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: menuViewController 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, menuViewController: UIViewController, menuPosition: ENSideMenuPosition, blurStyle: UIBlurEffectStyle = .Light) {
self.init(sourceView: sourceView, menuPosition: menuPosition, blurStyle: blurStyle)
self.menuViewController = menuViewController
self.menuViewController.view.frame = sideMenuContainerView.bounds
self.menuViewController.view.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
sideMenuContainerView.addSubview(self.menuViewController.view)
}
/*
public convenience init(sourceView: UIView, view: UIView, menuPosition: ENSideMenuPosition) {
self.init(sourceView: sourceView, menuPosition: menuPosition)
view.frame = sideMenuContainerView.bounds
view.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
sideMenuContainerView.addSubview(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 = CGRectMake(
(menuPosition == .Left) ?
isMenuOpen ? 0 : -menuWidth-1.0 :
isMenuOpen ? width - menuWidth : width+1.0,
sourceView.frame.origin.y,
menuWidth,
height
)
sideMenuContainerView.frame = menuFrame
}
private func adjustFrameDimensions( width: CGFloat, height: CGFloat ) -> (CGFloat,CGFloat) {
if floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1 &&
(UIApplication.sharedApplication().statusBarOrientation == UIInterfaceOrientation.LandscapeRight ||
UIApplication.sharedApplication().statusBarOrientation == UIInterfaceOrientation.LandscapeLeft) {
// iOS 7.1 or lower and landscape mode -> interchange width and height
return (height, width)
}
else {
return (width, height)
}
}
private func setupMenuView() {
// Configure side menu container
updateFrame()
sideMenuContainerView.backgroundColor = UIColor.clearColor()
sideMenuContainerView.clipsToBounds = false
sideMenuContainerView.layer.masksToBounds = false
sideMenuContainerView.layer.shadowOffset = (menuPosition == .Left) ? CGSizeMake(1.0, 1.0) : CGSizeMake(-1.0, -1.0)
sideMenuContainerView.layer.shadowRadius = 1.25
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
}
}
private 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) ? 20 : -20
boundaryPointX = (shouldOpen) ? menuWidth : -menuWidth-2
boundaryPointY = 20
}
else {
// Right side menu
gravityDirectionX = (shouldOpen) ? -1 : 1
pushMagnitude = (shouldOpen) ? -20 : 20
boundaryPointX = (shouldOpen) ? width-menuWidth : width+menuWidth+2
boundaryPointY = -20
}
let gravityBehavior = UIGravityBehavior(items: [sideMenuContainerView])
gravityBehavior.gravityDirection = CGVectorMake(gravityDirectionX, 0)
animator.addBehavior(gravityBehavior)
let collisionBehavior = UICollisionBehavior(items: [sideMenuContainerView])
collisionBehavior.addBoundaryWithIdentifier("menuBoundary", fromPoint: CGPointMake(boundaryPointX, boundaryPointY),
toPoint: CGPointMake(boundaryPointX, 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 = CGRectMake((shouldOpen) ? -2.0 : -menuWidth, 0, menuWidth, height)
}
else {
destFrame = CGRectMake((shouldOpen) ? width-menuWidth : width+2.0,
0,
menuWidth,
height)
}
UIView.animateWithDuration(
animationDuration,
animations: { () -> Void in
self.sideMenuContainerView.frame = destFrame
},
completion: { (Bool) -> Void in
if (self.isMenuOpen) {
self.delegate?.sideMenuDidOpen?()
} else {
self.delegate?.sideMenuDidClose?()
}
})
}
if (shouldOpen) {
delegate?.sideMenuWillOpen?()
} else {
delegate?.sideMenuWillClose?()
}
/*let 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)
sourceView.layer.zPosition = 0
outterView.userInteractionEnabled = shouldOpen */
}
public func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UISwipeGestureRecognizer {
let swipeGestureRecognizer = gestureRecognizer as! UISwipeGestureRecognizer
if !self.allowLeftSwipe {
if swipeGestureRecognizer.direction == .Left {
return false
}
}
if !self.allowRightSwipe {
if swipeGestureRecognizer.direction == .Right {
return false
}
}
}
else if gestureRecognizer.isEqual(panRecognizer) {
if allowPanGesture == false {
return false
}
animator.removeAllBehaviors()
let touchPosition = gestureRecognizer.locationOfTouch(0, inView: 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 > CGRectGetWidth(sourceView.frame) - menuWidth {
return true
}
}
else {
if touchPosition.x > CGRectGetWidth(sourceView.frame)-25 {
return true
}
}
}
return false
}
return true
}
internal func handleGesture(gesture: UISwipeGestureRecognizer) {
toggleMenu((self.menuPosition == .Right && gesture.direction == .Left)
|| (self.menuPosition == .Left && gesture.direction == .Right))
}
internal func handlePan(recognizer : UIPanGestureRecognizer){
let leftToRight = recognizer.velocityInView(recognizer.view).x > 0
switch recognizer.state {
case .Began:
break
case .Changed:
let translation = recognizer.translationInView(sourceView).x
let xPoint : CGFloat = sideMenuContainerView.center.x + translation + (menuPosition == .Left ? 1 : -1) * menuWidth / 2
if menuPosition == .Left {
if xPoint <= 0 || xPoint > CGRectGetWidth(self.sideMenuContainerView.frame) {
return
}
}else{
if xPoint <= sourceView.frame.size.width - menuWidth || xPoint >= sourceView.frame.size.width
{
return
}
}
sideMenuContainerView.center.x = sideMenuContainerView.center.x + translation
recognizer.setTranslation(CGPointZero, inView: sourceView)
default:
let shouldClose = menuPosition == .Left ? !leftToRight && CGRectGetMaxX(sideMenuContainerView.frame) < menuWidth : leftToRight && CGRectGetMinX(sideMenuContainerView.frame) > (sourceView.frame.size.width - menuWidth)
toggleMenu(!shouldClose)
}
}
private 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.
*/
public func toggleMenu () {
if (isMenuOpen) {
toggleMenu(false)
}
else {
updateSideMenuApperanceIfNeeded()
toggleMenu(true)
}
}
/**
Shows the side menu if the menu is hidden.
*/
public func showSideMenu () {
if (!isMenuOpen) {
toggleMenu(true)
}
}
/**
Hides the side menu if the menu is showed.
*/
public func hideSideMenu () {
if (isMenuOpen) {
toggleMenu(false)
}
}
}
extension ENSideMenu: UIDynamicAnimatorDelegate {
public func dynamicAnimatorDidPause(animator: UIDynamicAnimator) {
if (self.isMenuOpen) {
self.delegate?.sideMenuDidOpen?()
} else {
self.delegate?.sideMenuDidClose?()
}
}
public func dynamicAnimatorWillResume(animator: UIDynamicAnimator) {
print("resume")
}
}
This is my ENSideMenuNavigationController.swift
import UIKit
public class ENSideMenuNavigationController: UINavigationController, ENSideMenuProtocol {
public var sideMenu : ENSideMenu?
public var sideMenuAnimationType : ENSideMenuAnimation = .Default
// MARK: - Life cycle
public override func viewDidLoad() {
super.viewDidLoad()
}
public init( menuViewController: UIViewController, contentViewController: UIViewController?) {
super.init(nibName: nil, bundle: nil)
if (contentViewController != nil) {
self.viewControllers = [contentViewController!]
}
sideMenu = ENSideMenu(sourceView: self.view, menuViewController: menuViewController, menuPosition:.Right)
view.bringSubviewToFront(navigationBar)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
public override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Navigation
public func setContentViewController(contentViewController: UIViewController) {
self.sideMenu?.toggleMenu()
switch sideMenuAnimationType {
case .None:
self.viewControllers = [contentViewController]
break
default:
contentViewController.navigationItem.hidesBackButton = true
self.setViewControllers([contentViewController], animated: true)
break
}
}
}
This is my MyMenuTableViewController:
import UIKit
class MyMenuTableViewController: UITableViewController {
var selectedMenuItem : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Customize apperance of table view
tableView.contentInset = UIEdgeInsetsMake(116.0, 0, 0, 0) //
tableView.separatorStyle = .None
tableView.backgroundColor = UIColor.whiteColor()
tableView.scrollsToTop = false
tableView.allowsSelection = false
// Preserve selection between presentations
self.clearsSelectionOnViewWillAppear = false
tableView.selectRowAtIndexPath(NSIndexPath(forRow: selectedMenuItem, inSection: 0), animated: false, scrollPosition: .Middle)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CELL")
if (cell == nil) {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "CELL")
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkGrayColor()
let selectedBackgroundView = UIView(frame: CGRectMake(0, 0, cell!.frame.size.width, cell!.frame.size.height))
selectedBackgroundView.backgroundColor = UIColor.grayColor().colorWithAlphaComponent(0.2)
cell!.selectedBackgroundView = selectedBackgroundView
}
//cell!.textLabel?.text = "social Network #\(indexPath.row+1)"
cell!.imageView?.image = UIImage(named: "Facebook")
return cell!
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 45.0
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("did select row: \(indexPath.row)")
if (indexPath.row == selectedMenuItem) {
return
}
selectedMenuItem = indexPath.row
//Present new view controller
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main",bundle: nil)
var destViewController : UIViewController
switch (indexPath.row) {
case 0:
destViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController1")
break
case 1:
destViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController2")
break
case 2:
destViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController3")
break
default:
destViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController4")
break
}
sideMenuController()?.setContentViewController(destViewController)
}
}
This is my MyNavigationController.swift
import UIKit
class MyNavigationController: ENSideMenuNavigationController, ENSideMenuDelegate {
override func viewDidLoad() {
super.viewDidLoad()
sideMenu = ENSideMenu(sourceView: self.view, menuViewController: MyMenuTableViewController(), menuPosition:.Right)
//sideMenu?.delegate = self //optional
sideMenu?.menuWidth = 90.0 // optional, default is 160
// sideMenu?.bouncingEnabled = false
//sideMenu?.allowPanGesture = false
// make navigation bar showing over side menu
view.bringSubviewToFront(navigationBar)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - ENSideMenu Delegate
func sideMenuWillOpen() {
print("sideMenuWillOpen")
}
func sideMenuWillClose() {
print("sideMenuWillClose")
}
func sideMenuDidClose() {
print("sideMenuDidClose")
}
func sideMenuDidOpen() {
print("sideMenuDidOpen")
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
In your menu tableViewController you should implement
let MinHeight: CGFloat = 100.0
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let tHeight = tableView.bounds.height
let temp = tHeight/CGFloat(items.count) //Item size on your side menu
return temp > MinHeight ? temp : MinHeight
}