FlowingMenu/Sources/FlowingMenuTransitionManage...

218 lines
7.3 KiB
Swift

/*
* FlowingMenu
*
* Copyright 2015-present Yannick Loriot.
* http://yannickloriot.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
import UIKit
/**
Here manage the interactive transition mainly thanks to the gestures.
*/
extension FlowingMenuTransitionManager {
/**
Defines the given view as interactive to present the menu.
- parameter view: The view used to respond to the gesture events.
*/
public func setInteractivePresentationView(_ view: UIView) {
let screenEdgePanGesture = UIScreenEdgePanGestureRecognizer()
screenEdgePanGesture.edges = .left
screenEdgePanGesture.addTarget(self, action:#selector(FlowingMenuTransitionManager.panToPresentAction))
view.addGestureRecognizer(screenEdgePanGesture)
}
/**
Defines the given view as interactive to dismiss the menu.
- parameter view: The view used to respond to the gesture events.
*/
public func setInteractiveDismissView(_ view: UIView) {
let panGesture = UIPanGestureRecognizer()
panGesture.maximumNumberOfTouches = 1
panGesture.addTarget(self, action:#selector(FlowingMenuTransitionManager.panToDismissAction(_:)))
view.addGestureRecognizer(panGesture)
}
/**
Add the tap gesture to the given view to dismiss it when a tap occurred.
- parameter view: The view used to respond to the gesture events.
*/
func addTapGesture(_ view: UIView) {
let tapGesture = UITapGestureRecognizer()
tapGesture.numberOfTapsRequired = 1
tapGesture.addTarget(self, action: #selector(FlowingMenuTransitionManager.tapToDismissAction(_:)))
view.addGestureRecognizer(tapGesture)
}
// MARK: - Responding to Gesture Events
/**
The screen edge pan gesture recognizer action methods. It is used to
present the menu.
- parameter panGesture: The `UIScreenEdgePanGestureRecognizer` sender
object.
*/
@objc func panToPresentAction(_ panGesture: UIScreenEdgePanGestureRecognizer) {
let view = panGesture.view!
let translation = panGesture.translation(in: view)
let menuWidth = (delegate ?? self).flowingMenu(self, widthOfMenuView: view)
let yLocation = panGesture.location(in: panGesture.view).y
let percentage = min(max(translation.x / (menuWidth / 2), 0), 1)
switch panGesture.state {
case .began:
interactive = true
// Asking the delegate the present the menu
delegate?.flowingMenuNeedsPresentMenu(self)
fallthrough
case .changed:
update(percentage)
let waveWidth = translation.x * 0.9
let left = waveWidth * 0.1
// Update the control points
moveControlViewsToPoint(CGPoint(x: left, y: yLocation), waveWidth: waveWidth)
// Update the shape layer
updateShapeLayer()
default:
animating = true
if percentage < 1 {
interactive = false
moveControlViewsToPoint(CGPoint(x: 0, y: yLocation), waveWidth: 0)
cancel()
}
else {
finish()
}
}
}
/**
The pan gesture recognizer action methods. It is used to dismiss the
menu.
- parameter panGesture: The `UIPanGestureRecognizer` sender object.
*/
@objc func panToDismissAction(_ panGesture: UIPanGestureRecognizer) {
let view = panGesture.view!
let translation = panGesture.translation(in: view)
let menuWidth = (delegate ?? self).flowingMenu(self, widthOfMenuView: view)
let percentage = min(max(translation.x / menuWidth * -1, 0), 1)
switch panGesture.state {
case .began:
interactive = true
delegate?.flowingMenuNeedsDismissMenu(self)
case .changed:
update(percentage)
default:
interactive = false
if percentage > 0.2 {
finish()
}
else {
cancel()
}
}
}
/**
The tap gesture recognizer action methods. It is used to dismiss the
menu.
- parameter tapGesture: The `UITapGestureRecognizer` sender object.
*/
@objc func tapToDismissAction(_ tapGesture: UITapGestureRecognizer) {
delegate?.flowingMenuNeedsDismissMenu(self)
}
// MARK: - Building Paths
/**
Returns a bezier path using the control view positions.
- returns: A bezier path.
*/
func currentPath() -> CGPath {
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: controlViews[0].center(animating).x, y: 0))
bezierPath.addCurve(to: controlViews[2].center(animating), controlPoint1: controlViews[0].center(animating), controlPoint2: controlViews[1].center(animating))
bezierPath.addCurve(to: controlViews[4].center(animating), controlPoint1: controlViews[3].center(animating), controlPoint2: controlViews[4].center(animating))
bezierPath.addCurve(to: controlViews[6].center(animating), controlPoint1: controlViews[4].center(animating), controlPoint2: controlViews[5].center(animating))
bezierPath.addLine(to: CGPoint(x: 0, y: controlViews[7].center.y))
bezierPath.close()
return bezierPath.cgPath
}
// MARK: - Updating Shapes
/// Update the shape layer using the current control view positions.
@objc func updateShapeLayer() {
shapeLayer.path = currentPath()
}
/**
Move the control view positions using a position and a wave width.
- parameter position: The target position.
- parameter waveWidth: The wave width in point.
*/
func moveControlViewsToPoint(_ position: CGPoint, waveWidth: CGFloat) {
let height = controlViews[7].center.y
let minTopY = min((position.y - height / 2) * 0.28, 0)
let maxBottomY = max(height + (position.y - height / 2) * 0.28, height)
let leftPartWidth = position.y - minTopY
let rightPartWidth = maxBottomY - position.y
controlViews[0].center = CGPoint(x: position.x, y: minTopY)
controlViews[1].center = CGPoint(x: position.x, y: minTopY + leftPartWidth * 0.44)
controlViews[2].center = CGPoint(x: position.x + waveWidth * 0.64, y: minTopY + leftPartWidth * 0.71)
controlViews[3].center = CGPoint(x: position.x + waveWidth * 1.36, y: position.y)
controlViews[4].center = CGPoint(x: position.x + waveWidth * 0.64, y: maxBottomY - rightPartWidth * 0.71)
controlViews[5].center = CGPoint(x: position.x, y: maxBottomY - (rightPartWidth * 0.44))
controlViews[6].center = CGPoint(x: position.x, y: height)
}
}