I want to know what do you think about the structure of this code. If you think that there is some structural improvement to be made, tell me!
Here is a github link: https://github.com/LucianoPolit/LPDropdownMenu
Here is the code:
//
// LPDropdownMenu.swift
// Created by Luciano Polit on 1/8/16.
// Copyright (c) 2016 Luciano Polit. All rights reserved.
//
import UIKit
@objc protocol LPDropdownMenuViewDelegate {
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, shouldSwipeToPosition position: CGFloat) -> Bool
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, shouldTouchToPosition position: CGFloat) -> Bool
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, shouldDoubleTouchToPosition position: CGFloat) -> Bool
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, shouldAproximateToPosition position: CGFloat) -> Bool
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, shouldFinishSlidingAtPosition position: CGFloat) -> Bool
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, willSwipeToPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, willTouchToPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, willDoubleTouchToPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, willAproximateToPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, willFinishSlidingAtPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, didSwipeToPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, didTouchToPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, didDoubleTouchToPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, didAproximateToPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, didFinishSlidingAtPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, didStartMovingAtPosition position: CGFloat)
optional func dropdownMenuView(dropdownMenuView: LPDropdownMenuView, didFinishMovingAtPosition position: CGFloat)
}
class LPDropdownMenuView: UIView {
private var panGestureRecognizer: UIPanGestureRecognizer!
private var tapGestureRecognizer: UITapGestureRecognizer!
private var doubleTapGestureRecognizer: UITapGestureRecognizer!
private var movingCondition: Bool = false
var delegate: LPDropdownMenuViewDelegate?
var freeSlideable: Bool = true
var swipeable: Bool = true
var touchable: Bool = true
var doubleTouchable: Bool = true
var autoSetProperties: Bool = true
var marginProximity: CGFloat = 50
var swipeSensibility: CGFloat = 600
var minPosition: CGFloat!
var maxPosition: CGFloat!
var contentFrame: CGRect! {
didSet {
setProperties()
}
}
var barView: UIView! {
willSet {
if (self.barView != nil) {
self.barView.removeFromSuperview()
}
}
didSet {
let height = barView.frame.size.height + (contentFrame.maxY - barView.frame.maxY)
frame = CGRectMake(barView.frame.origin.x, barView.frame.origin.y, barView.frame.size.width, height)
barView.frame.origin.y = 0
addSubview(barView)
setPosition(frame.origin.y)
setProperties()
setGestures()
}
}
var menuView: UIView! {
willSet {
if (self.menuView != nil) {
self.menuView.removeFromSuperview()
}
}
didSet {
if (!(menuView is UIScrollView)) {
let newValue = menuView
newValue.frame.origin.y = 0
let height = frame.size.height - barView.frame.size.height
menuView = UIScrollView(frame: CGRectMake(0, barView.frame.size.height, newValue.frame.size.width, height))
(menuView as! UIScrollView).contentSize = CGSize(width: newValue.frame.size.width, height: newValue.frame.size.height)
(menuView as! UIScrollView).bounces = false
menuView.addSubview(newValue)
} else {
let height = frame.size.height - barView.frame.size.height
menuView.frame = CGRectMake(0, barView.frame.size.height, menuView.frame.size.width, height)
}
addSubview(menuView)
}
}
init (frame: CGRect, barView: UIView, menuView: UIView) {
super.init(frame: frame)
self.contentFrame = frame
initView(barView, menuView: menuView)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.contentFrame = frame
let barView = DefaultBarView(frame: CGRectMake(0, frame.size.height - 60, frame.size.width, 60))
let menuView = DefaultMenuView(frame: CGRectMake(0, barView.frame.size.height, frame.size.width, 600))
initView(barView, menuView: menuView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func initView(barView: UIView, menuView: UIView) {
self.initGestures()
self.barView = barView
self.menuView = menuView
}
}
extension LPDropdownMenuView {
func getPosition() -> CGFloat {
return frame.origin.y
}
func setProperties() {
if (contentFrame != nil && autoSetProperties) {
minPosition = contentFrame.maxY / 3
maxPosition = contentFrame.maxY - barView.frame.size.height
}
}
func setPosition(position: CGFloat) {
self.frame.origin.y = position
self.frame.size.height = self.contentFrame.size.height - position
if (menuView != nil) {
self.menuView.frame.size.height = self.contentFrame.size.height - self.barView.frame.size.height - position
}
}
func setPositionWithAnimation(position: CGFloat, time: NSTimeInterval, completion: ((Void) -> Void)?) {
UIView.animateWithDuration(time, delay: 0, options: .CurveEaseOut, animations: {
self.frame.origin.y = position
self.frame.size.height = self.contentFrame.size.height - position
self.menuView.frame.size.height = self.contentFrame.size.height - position - self.barView.frame.size.height
}, completion: { _ in
if (completion != nil) {
completion!()
}
})
}
}
extension LPDropdownMenuView {
private func initGestures() {
doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: "handleDoubleTapGestures:")
doubleTapGestureRecognizer.numberOfTapsRequired = 2
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "handleTapGestures:")
tapGestureRecognizer.numberOfTapsRequired = 1
tapGestureRecognizer.requireGestureRecognizerToFail(doubleTapGestureRecognizer)
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: "handlePanGestures:")
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
}
private func setGestures() {
barView.addGestureRecognizer(doubleTapGestureRecognizer)
barView.addGestureRecognizer(tapGestureRecognizer)
barView.addGestureRecognizer(panGestureRecognizer)
}
internal func handleDoubleTapGestures(sender: UITapGestureRecognizer) {
if (doubleTouchable) {
let minPosition = contentFrame.origin.y
let maxPosition = contentFrame.maxY - barView.frame.size.height
let fartherPosition = getFartherPosition(minPosition, maxPosition: maxPosition)
if (delegate?.dropdownMenuView?(self, shouldDoubleTouchToPosition: fartherPosition) != false) {
delegate?.dropdownMenuView?(self, willDoubleTouchToPosition: fartherPosition)
setPositionWithAnimation(fartherPosition, time: 0.3, completion: {
delegate?.dropdownMenuView?(self, didDoubleTouchToPosition: fartherPosition)
})
}
}
}
internal func handleTapGestures(sender: UITapGestureRecognizer) {
if (touchable) {
let fartherPosition = getFartherPosition(minPosition, maxPosition: maxPosition)
if (delegate?.dropdownMenuView?(self, shouldTouchToPosition: fartherPosition) != false) {
delegate?.dropdownMenuView?(self, willTouchToPosition: fartherPosition)
setPositionWithAnimation(fartherPosition, time: 0.3, completion: {
delegate?.dropdownMenuView?(self, didTouchToPosition: fartherPosition)
})
}
}
}
internal func handlePanGestures(sender: UIPanGestureRecognizer) {
let location = sender.locationInView(sender.view!.superview!)
if (sender.state != .Ended && sender.state != .Failed) {
if (!movingCondition) {
movingCondition = true
delegate?.dropdownMenuView?(self, didStartMovingAtPosition: frame.origin.y)
}
let minPosition = contentFrame.origin.y
let maxPosition = contentFrame.maxY - barView.frame.size.height
let newPosition = frame.origin.y + location.y - (sender.view!.frame.size.height / 2)
if (newPosition > minPosition && newPosition < maxPosition) {
setPosition(newPosition)
} else {
if (newPosition < minPosition) {
setPosition(minPosition)
} else {
setPosition(maxPosition)
}
}
}
if (sender.state == .Ended) {
movingCondition = false
delegate?.dropdownMenuView?(self, didFinishMovingAtPosition: frame.origin.y)
if (checkMarginProximity(contentFrame.origin.y)) {
if (delegate?.dropdownMenuView?(self, shouldAproximateToPosition: contentFrame.origin.y) != false) {
delegate?.dropdownMenuView?(self, willAproximateToPosition: contentFrame.origin.y)
setPositionWithAnimation(contentFrame.origin.y, time: 0.3, completion: {
delegate?.dropdownMenuView?(self, didAproximateToPosition: contentFrame.origin.y)
})
return
}
}
if (checkMarginProximity(contentFrame.maxY - barView.frame.size.height)) {
let proximatePosition = contentFrame.maxY - barView.frame.size.height
if (delegate?.dropdownMenuView?(self, shouldAproximateToPosition: proximatePosition) != false) {
delegate?.dropdownMenuView?(self, willAproximateToPosition: proximatePosition)
setPositionWithAnimation(contentFrame.maxY - barView.frame.size.height, time: 0.3, completion: {
delegate?.dropdownMenuView?(self, didAproximateToPosition: proximatePosition)
})
return
}
}
if (swipeable) {
let swipePosition = getSwipePosition(sender.velocityInView(sender.view))
if (swipePosition != nil) {
let swipeCondition = delegate?.dropdownMenuView?(self, shouldSwipeToPosition: swipePosition!) != false
if (swipeCondition) {
delegate?.dropdownMenuView?(self, willSwipeToPosition: swipePosition!)
setPositionWithAnimation(swipePosition!, time: 0.3, completion: {
delegate?.dropdownMenuView?(self, didSwipeToPosition: swipePosition!)
})
return
}
}
}
if (freeSlideable && delegate?.dropdownMenuView?(self, shouldFinishSlidingAtPosition: frame.origin.y) == false) {
let nearestPosition = getNearestPosition()
delegate?.dropdownMenuView?(self, willAproximateToPosition: nearestPosition)
setPositionWithAnimation(nearestPosition, time: 0.3, completion: {
delegate?.dropdownMenuView?(self, didAproximateToPosition: nearestPosition)
})
return
}
if (!freeSlideable) {
let nearestPosition = getNearestPosition()
delegate?.dropdownMenuView?(self, willAproximateToPosition: nearestPosition)
setPositionWithAnimation(nearestPosition, time: 0.3, completion: {
delegate?.dropdownMenuView?(self, didAproximateToPosition: nearestPosition)
})
return
}
delegate?.dropdownMenuView?(self, willFinishSlidingAtPosition: frame.origin.y)
delegate?.dropdownMenuView?(self, didFinishSlidingAtPosition: frame.origin.y)
}
}
private func checkMarginProximity(position: CGFloat) -> Bool {
if (frame.origin.y < position + marginProximity && frame.origin.y > position - marginProximity) {
return true
}
return false
}
private func getNearestPosition() -> CGFloat {
let currentPosition = frame.origin.y + (barView.frame.size.height / 2)
let positions = [contentFrame.origin.y, minPosition, maxPosition, contentFrame.maxY]
var nearestPosition: CGFloat!
var distance: CGFloat!
for position in positions {
if (distance == nil || distance > abs(currentPosition - position)) {
distance = abs(currentPosition - position)
nearestPosition = position
}
}
return nearestPosition
}
private func getSwipePosition(velocity: CGPoint) -> CGFloat? {
if (velocity.y > swipeSensibility) {
return maxPosition
}
if (velocity.y < -swipeSensibility) {
if (frame.origin.y < minPosition) {
return contentFrame.origin.y
} else {
return minPosition
}
}
return nil
}
private func getFartherPosition(minPosition: CGFloat, maxPosition: CGFloat) -> CGFloat {
let rangeMinPosition: CGFloat = 1.5
let rangeMaxPosition: CGFloat = 0.5
let distanceFromMin = (frame.origin.y - minPosition) * rangeMinPosition
let distanceFromMax = (maxPosition - frame.origin.y) * rangeMaxPosition
if (distanceFromMin > distanceFromMax) {
return minPosition
} else {
return maxPosition
}
}
}
class DefaultBarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.blueColor()
initLabel()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func initLabel() {
let label = UILabel(frame: CGRectMake(0, 0, frame.size.width, frame.size.height))
label.textAlignment = .Center
label.textColor = UIColor.whiteColor()
label.font = UIFont.systemFontOfSize(22)
label.text = "Something cool here!"
addSubview(label)
}
}
class DefaultMenuView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.grayColor()
initLabel()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func initLabel() {
let label = UILabel(frame: CGRectMake(0, 0, frame.size.width, frame.size.height))
label.textAlignment = .Center
label.textColor = UIColor.whiteColor()
label.font = UIFont.systemFontOfSize(22)
label.text = "An amazing view here!"
addSubview(label)
}
}
The code works perfect, but I want your opinion!