Commit ffaaea85 by Daniel Dahan

development: added BottomSheetController and BottomSheet

parent 4b8a39b8
......@@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
961730361E0E156400A9A297 /* SpringMotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961730351E0E156400A9A297 /* SpringMotion.swift */; };
9617305A1E145DE900A9A297 /* CollectionViewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961730591E145DE900A9A297 /* CollectionViewCard.swift */; };
961730701E16EFFF00A9A297 /* BottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9617306F1E16EFFF00A9A297 /* BottomSheet.swift */; };
961730721E16F00C00A9A297 /* BottomSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961730711E16F00C00A9A297 /* BottomSheetController.swift */; };
9617B07D1DFCA8CF00410F8F /* Application.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961E6BDE1DDA2A95004E6C93 /* Application.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B07E1DFCA8CF00410F8F /* Card.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB75D1CB40DC500C806FE /* Card.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B07F1DFCA8CF00410F8F /* ImageCard.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7621CB40DC500C806FE /* ImageCard.swift */; settings = {ATTRIBUTES = (Public, ); }; };
......@@ -228,6 +230,8 @@
961276621DCD8B1800A7D920 /* CharacterAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterAttribute.swift; sourceTree = "<group>"; };
961730351E0E156400A9A297 /* SpringMotion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringMotion.swift; sourceTree = "<group>"; };
961730591E145DE900A9A297 /* CollectionViewCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCard.swift; sourceTree = "<group>"; };
9617306F1E16EFFF00A9A297 /* BottomSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomSheet.swift; sourceTree = "<group>"; };
961730711E16F00C00A9A297 /* BottomSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomSheetController.swift; sourceTree = "<group>"; };
961DED451DCC40C500F425B6 /* Editor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Editor.swift; sourceTree = "<group>"; };
961DED4A1DCC546100F425B6 /* EditorController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorController.swift; sourceTree = "<group>"; };
961E6BDE1DDA2A95004E6C93 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
......@@ -386,6 +390,15 @@
name = TextField;
sourceTree = "<group>";
};
9617306E1E16EFE300A9A297 /* BottomSheet */ = {
isa = PBXGroup;
children = (
9617306F1E16EFFF00A9A297 /* BottomSheet.swift */,
961730711E16F00C00A9A297 /* BottomSheetController.swift */,
);
name = BottomSheet;
sourceTree = "<group>";
};
961DED441DCC40AC00F425B6 /* Editor */ = {
isa = PBXGroup;
children = (
......@@ -627,6 +640,7 @@
children = (
961E6BDD1DDA2A7E004E6C93 /* Application */,
96264BE41D833C8400576F37 /* Bar */,
9617306E1E16EFE300A9A297 /* BottomSheet */,
962DDD081D6FBBD0001C307C /* BottomTabBar */,
96BCB8031CB40F4B00C806FE /* Button */,
96BCB8021CB40F3B00C806FE /* Card */,
......@@ -1200,6 +1214,7 @@
965E80E51DD4C53300D61E4B /* PulseMotion.swift in Sources */,
965E80FE1DD4D59500D61E4B /* ToolbarController.swift in Sources */,
96328B971E05C0BB009A4C90 /* TableView.swift in Sources */,
961730701E16EFFF00A9A297 /* BottomSheet.swift in Sources */,
965E80C81DD4C50600D61E4B /* Motion.swift in Sources */,
965E80C91DD4C50600D61E4B /* TransitionMotion.swift in Sources */,
965E80CA1DD4C50600D61E4B /* KeyframeMotion.swift in Sources */,
......@@ -1222,6 +1237,7 @@
965E80D51DD4C50600D61E4B /* Grid.swift in Sources */,
965E80D61DD4C50600D61E4B /* HeightPreset.swift in Sources */,
961E6BDF1DDA2A95004E6C93 /* Application.swift in Sources */,
961730721E16F00C00A9A297 /* BottomSheetController.swift in Sources */,
965E80D71DD4C50600D61E4B /* Icon.swift in Sources */,
965E80FC1DD4D59500D61E4B /* SearchBarController.swift in Sources */,
965E80D81DD4C50600D61E4B /* Layer.swift in Sources */,
......
/*
* Copyright (C) 2015 - 2016, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
class BottomSheet: CollectionViewCard {
}
/*
* Copyright (C) 2015 - 2016, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(BottomSheetPosition)
public enum BottomSheetPosition: Int {
case left
case right
}
extension UIViewController {
/**
A convenience property that provides access to the BottomSheetController.
This is the recommended method of accessing the BottomSheetController
through child UIViewControllers.
*/
public var bottomSheetController: BottomSheetController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is BottomSheetController {
return viewController as? BottomSheetController
}
viewController = viewController?.parent
}
return nil
}
}
@objc(BottomSheetControllerDelegate)
public protocol BottomSheetControllerDelegate {
/**
An optional delegation method that is fired before the
BottomSheetController opens.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter position: The BottomSheetPosition.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, willOpen position: BottomSheetPosition)
/**
An optional delegation method that is fired after the
BottomSheetController opened.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter position: The BottomSheetPosition.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, didOpen position: BottomSheetPosition)
/**
An optional delegation method that is fired before the
BottomSheetController closes.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter position: The BottomSheetPosition.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, willClose position: BottomSheetPosition)
/**
An optional delegation method that is fired after the
BottomSheetController closed.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter position: The BottomSheetPosition.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, didClose position: BottomSheetPosition)
/**
An optional delegation method that is fired when the
BottomSheetController pan gesture begins.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter didBeginPanAt point: A CGPoint.
- Parameter position: The BottomSheetPosition.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, didBeginPanAt point: CGPoint, position: BottomSheetPosition)
/**
An optional delegation method that is fired when the
BottomSheetController pan gesture changes position.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter didChangePanAt point: A CGPoint.
- Parameter position: The BottomSheetPosition.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, didChangePanAt point: CGPoint, position: BottomSheetPosition)
/**
An optional delegation method that is fired when the
BottomSheetController pan gesture ends.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter didEndPanAt point: A CGPoint.
- Parameter position: The BottomSheetPosition.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, didEndPanAt point: CGPoint, position: BottomSheetPosition)
/**
An optional delegation method that is fired when the
BottomSheetController tap gesture executes.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter didTapAt point: A CGPoint.
- Parameter position: The BottomSheetPosition.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, didTapAt point: CGPoint, position: BottomSheetPosition)
/**
An optional delegation method that is fired when the
status bar is about to change display, isHidden or not.
- Parameter bottomSheetController: A BottomSheetController.
- Parameter statusBar isHidden: A boolean.
*/
@objc
optional func bottomSheetController(bottomSheetController: BottomSheetController, statusBar isHidden: Bool)
}
@objc(BottomSheetController)
open class BottomSheetController: RootController {
/**
A CGFloat property that is used internally to track
the original (x) position of the container view when panning.
*/
fileprivate var originalX: CGFloat = 0
/**
A UIPanGestureRecognizer property internally used for the
bottomView pan gesture.
*/
internal fileprivate(set) var bottomPanGesture: UIPanGestureRecognizer?
/**
A UITapGestureRecognizer property internally used for the
bottomView tap gesture.
*/
internal fileprivate(set) var bottomTapGesture: UITapGestureRecognizer?
/**
A CGFloat property that accesses the bottomView threshold of
the BottomSheetController. When the panning gesture has
ended, if the position is beyond the threshold,
the bottomView is opened, if it is below the threshold, the
bottomView is closed.
*/
@IBInspectable
open var bottomThreshold: CGFloat = 64
fileprivate var bottomViewThreshold: CGFloat = 0
/**
A BottomSheetControllerDelegate property used to bind
the delegation object.
*/
open weak var delegate: BottomSheetControllerDelegate?
/**
A CGFloat property that sets the animation duration of the
bottomView when closing and opening. Defaults to 0.25.
*/
@IBInspectable
open var animationDuration: TimeInterval = 0.25
/**
A Boolean property that enables and disables the bottomView from
opening and closing. Defaults to true.
*/
@IBInspectable
open var isEnabled: Bool {
get {
return isBottomViewEnabled
}
set(value) {
if nil != bottomView {
isBottomViewEnabled = value
}
}
}
/**
A Boolean property that enables and disables the bottomView from
opening and closing. Defaults to true.
*/
@IBInspectable
open var isBottomViewEnabled = false {
didSet {
isBottomPanGestureEnabled = isBottomViewEnabled
isBottomTapGestureEnabled = isBottomViewEnabled
}
}
/// Enables the left pan gesture.
@IBInspectable
open var isBottomPanGestureEnabled = false {
didSet {
if isBottomPanGestureEnabled {
prepareBottomPanGesture()
} else {
removeBottomPanGesture()
}
}
}
/// Enables the left tap gesture.
@IBInspectable
open var isBottomTapGestureEnabled = false {
didSet {
if isBottomTapGestureEnabled {
prepareBottomTapGesture()
} else {
removeBottomTapGesture()
}
}
}
/**
A DepthPreset property that is used to set the depth of the
bottomView when opened.
*/
open var depthPreset = DepthPreset.depth1
/**
A UIView property that is used to hide and reveal the
bottomViewController. It is very rare that this property will
need to be accessed externally.
*/
open fileprivate(set) var bottomView: UIView?
/// Indicates whether the bottomView or rightView is opened.
open var isOpened: Bool {
return isBottomViewOpened
}
/// indicates if the bottomView is opened.
open var isBottomViewOpened: Bool {
guard nil != bottomView else {
return false
}
return bottomView!.x != -bottomViewWidth
}
/**
A UIViewController property that references the
active left UIViewController.
*/
open fileprivate(set) var bottomViewController: UIViewController?
/**
A CGFloat property to access the width that the bottomView
opens up to.
*/
@IBInspectable
open fileprivate(set) var bottomViewWidth: CGFloat!
/**
An initializer that initializes the object with a NSCoder object.
- Parameter aDecoder: A NSCoder instance.
*/
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepare()
}
/**
An initializer that initializes the object with an Optional nib and bundle.
- Parameter nibNameOrNil: An Optional String for the nib.
- Parameter bundle: An Optional NSBundle where the nib is located.
*/
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
prepare()
}
/**
An initializer for the BottomSheetController.
- Parameter rootViewController: The main UIViewController.
- Parameter bottomViewController: An Optional left UIViewController.
*/
public init(rootViewController: UIViewController, bottomViewController: UIViewController? = nil) {
super.init(rootViewController: rootViewController)
self.bottomViewController = bottomViewController
prepare()
}
/// Layout subviews.
open override func layoutSubviews() {
super.layoutSubviews()
guard let v = bottomView else {
return
}
v.width = bottomViewWidth
v.height = view.bounds.height
bottomViewThreshold = bottomViewWidth / 2
guard let vc = bottomViewController else {
return
}
vc.view.width = v.width
vc.view.height = v.height
vc.view.center = CGPoint(x: v.width / 2, y: v.height / 2)
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() {
super.prepare()
prepareBottomView()
}
/**
A method that is used to set the width of the bottomView when
opened. This is the recommended method of setting the bottomView
width.
- Parameter width: A CGFloat value to set as the new width.
- Parameter isHidden: A Boolean value of whether the bottomView
should be isHidden after the width has been updated or not.
- Parameter animated: A Boolean value that indicates to animate
the bottomView width change.
*/
open func setBottomViewHeight(width: CGFloat, isHidden: Bool, animated: Bool, duration: TimeInterval = 0.5) {
guard let v = bottomView else {
return
}
bottomViewWidth = width
if animated {
v.isShadowPathAutoSizing = false
if isHidden {
UIView.animate(withDuration: duration,
animations: { [weak self] in
guard let s = self else {
return
}
v.bounds.size.width = width
v.position.x = -width / 2
s.rootViewController.view.alpha = 1
}) { [weak self] _ in
guard let s = self else {
return
}
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.hideView(container: v)
}
} else {
UIView.animate(withDuration: duration,
animations: { [weak self] in
guard let s = self else {
return
}
v.bounds.size.width = width
v.position.x = width / 2
s.rootViewController.view.alpha = 0.5
}) { [weak self] _ in
guard let s = self else {
return
}
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.showView(container: v)
}
}
} else {
v.bounds.size.width = width
if isHidden {
hideView(container: v)
v.position.x = -v.width / 2
rootViewController.view.alpha = 1
} else {
v.isShadowPathAutoSizing = false
showView(container: v)
v.position.x = width / 2
rootViewController.view.alpha = 0.5
v.isShadowPathAutoSizing = true
}
layoutSubviews()
}
}
/**
A method that toggles the bottomView opened if previously closed,
or closed if previously opened.
- Parameter velocity: A CGFloat value that sets the
velocity of the user interaction when animating the
bottomView. Defaults to 0.
*/
open func toggleBottomView(velocity: CGFloat = 0) {
isBottomViewOpened ? closeBottomView(velocity: velocity) : openBottomView(velocity: velocity)
}
/**
A method that opens the bottomView.
- Parameter velocity: A CGFloat value that sets the
velocity of the user interaction when animating the
bottomView. Defaults to 0.
*/
open func openBottomView(velocity: CGFloat = 0) {
guard isBottomViewEnabled else {
return
}
guard let v = bottomView else {
return
}
showView(container: v)
isUserInteractionEnabled = false
delegate?.bottomSheetController?(bottomSheetController: self, willOpen: .left)
UIView.animate(withDuration: TimeInterval(0 == velocity ? animationDuration : fmax(0.1, fmin(1, Double(v.x / velocity)))),
animations: { [weak self, v = v] in
guard let s = self else {
return
}
v.position.x = v.width / 2
s.rootViewController.view.alpha = 0.5
}) { [weak self] _ in
guard let s = self else {
return
}
s.delegate?.bottomSheetController?(bottomSheetController: s, didOpen: .left)
}
}
/**
A method that closes the bottomView.
- Parameter velocity: A CGFloat value that sets the
velocity of the user interaction when animating the
bottomView. Defaults to 0.
*/
open func closeBottomView(velocity: CGFloat = 0) {
guard isBottomViewEnabled else {
return
}
guard let v = bottomView else {
return
}
isUserInteractionEnabled = true
delegate?.bottomSheetController?(bottomSheetController: self, willClose: .left)
UIView.animate(withDuration: TimeInterval(0 == velocity ? animationDuration : fmax(0.1, fmin(1, Double(v.x / velocity)))),
animations: { [weak self, v = v] in
guard let s = self else {
return
}
v.position.x = -v.width / 2
s.rootViewController.view.alpha = 1
}) { [weak self] _ in
guard let s = self else {
return
}
s.hideView(container: v)
s.delegate?.bottomSheetController?(bottomSheetController: s, didClose: .left)
}
}
/// A method that removes the passed in pan and bottomView tap gesture recognizers.
fileprivate func removeBottomViewGestures() {
removeBottomPanGesture()
removeBottomTapGesture()
}
/// Removes the left pan gesture.
fileprivate func removeBottomPanGesture() {
guard let v = bottomPanGesture else {
return
}
view.removeGestureRecognizer(v)
bottomPanGesture = nil
}
/// Removes the left tap gesture.
fileprivate func removeBottomTapGesture() {
guard let v = bottomTapGesture else {
return
}
view.removeGestureRecognizer(v)
bottomTapGesture = nil
}
/**
A method that determines whether the passed point is
contained within the bounds of the bottomViewThreshold
and height of the BottomSheetController view frame
property.
- Parameter point: A CGPoint to test against.
- Returns: A Boolean of the result, true if yes, false
otherwise.
*/
fileprivate func isPointContainedWithinBottomThreshold(point: CGPoint) -> Bool {
return point.x <= bottomThreshold
}
/**
A method that determines whether the passed in point is
contained within the bounds of the passed in container view.
- Parameter container: A UIView that sets the bounds to test
against.
- Parameter point: A CGPoint to test whether or not it is
within the bounds of the container parameter.
- Returns: A Boolean of the result, true if yes, false
otherwise.
*/
fileprivate func isPointContainedWithinView(container: UIView, point: CGPoint) -> Bool {
return container.bounds.contains(point)
}
/**
A method that shows a view.
- Parameter container: A container view.
*/
fileprivate func showView(container: UIView) {
container.depthPreset = depthPreset
container.isHidden = false
}
/**
A method that hides a view.
- Parameter container: A container view.
*/
fileprivate func hideView(container: UIView) {
container.depthPreset = .none
container.isHidden = true
}
}
extension BottomSheetController {
/// A method that prepares the bottomViewController.
fileprivate func prepareBottomViewController() {
guard let v = bottomView else {
return
}
prepare(viewController: bottomViewController, withContainer: v)
}
/// A method that prepares the bottomView.
fileprivate func prepareBottomView() {
guard nil != bottomViewController else {
return
}
isBottomViewEnabled = true
bottomViewWidth = .phone == Device.userInterfaceIdiom ? 280 : 320
bottomView = UIView()
bottomView!.frame = CGRect(x: 0, y: 0, width: bottomViewWidth, height: view.height)
bottomView!.backgroundColor = nil
view.addSubview(bottomView!)
bottomView!.isHidden = true
bottomView!.position.x = -bottomViewWidth / 2
bottomView!.zPosition = 2000
prepareBottomViewController()
}
/// Prepare the left pan gesture.
fileprivate func prepareBottomPanGesture() {
guard nil == bottomPanGesture else {
return
}
bottomPanGesture = UIPanGestureRecognizer(target: self, action: #selector(handleBottomViewPanGesture(recognizer:)))
bottomPanGesture!.delegate = self
view.addGestureRecognizer(bottomPanGesture!)
}
/// Prepare the left tap gesture.
fileprivate func prepareBottomTapGesture() {
guard nil == bottomTapGesture else {
return
}
bottomTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBottomViewTapGesture(recognizer:)))
bottomTapGesture!.delegate = self
bottomTapGesture!.cancelsTouchesInView = false
view.addGestureRecognizer(bottomTapGesture!)
}
}
extension BottomSheetController: UIGestureRecognizerDelegate {
/**
Detects the gesture recognizer being used.
- Parameter gestureRecognizer: A UIGestureRecognizer to detect.
- Parameter touch: The UITouch event.
- Returns: A Boolean of whether to continue the gesture or not.
*/
@objc
open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if gestureRecognizer == bottomPanGesture && (isBottomViewOpened || isPointContainedWithinBottomThreshold(point: touch.location(in: view))) {
return true
}
if isBottomViewOpened && gestureRecognizer == bottomTapGesture {
return true
}
return false
}
/**
A method that is fired when the pan gesture is recognized
for the bottomView.
- Parameter recognizer: A UIPanGestureRecognizer that is
passed to the handler when recognized.
*/
@objc
fileprivate func handleBottomViewPanGesture(recognizer: UIPanGestureRecognizer) {
guard isBottomViewEnabled && (isBottomViewOpened || isPointContainedWithinBottomThreshold(point: recognizer.location(in: view))) else {
return
}
guard let v = bottomView else {
return
}
let point = recognizer.location(in: view)
// Animate the panel.
switch recognizer.state {
case .began:
originalX = v.position.x
showView(container: v)
delegate?.bottomSheetController?(bottomSheetController: self, didBeginPanAt: point, position: .left)
case .changed:
let w = v.width
let translationX = recognizer.translation(in: v).x
v.position.x = originalX + translationX > (w / 2) ? (w / 2) : originalX + translationX
let a = 1 - v.position.x / v.width
rootViewController.view.alpha = 0.5 < a && v.position.x <= v.width / 2 ? a : 0.5
delegate?.bottomSheetController?(bottomSheetController: self, didChangePanAt: point, position: .left)
case .ended, .cancelled, .failed:
let p = recognizer.velocity(in: recognizer.view)
let x = p.x >= 1000 || p.x <= -1000 ? p.x : 0
delegate?.bottomSheetController?(bottomSheetController: self, didEndPanAt: point, position: .left)
if v.x <= -bottomViewWidth + bottomViewThreshold || x < -1000 {
closeBottomView(velocity: x)
} else {
openBottomView(velocity: x)
}
case .possible:break
}
}
/**
A method that is fired when the tap gesture is recognized
for the bottomView.
- Parameter recognizer: A UITapGestureRecognizer that is
passed to the handler when recognized.
*/
@objc
fileprivate func handleBottomViewTapGesture(recognizer: UITapGestureRecognizer) {
guard isBottomViewOpened else {
return
}
guard let v = bottomView else {
return
}
delegate?.bottomSheetController?(bottomSheetController: self, didTapAt: recognizer.location(in: view), position: .left)
guard isBottomViewEnabled && isBottomViewOpened && !isPointContainedWithinView(container: v, point: recognizer.location(in: v)) else {
return
}
closeBottomView()
}
}
......@@ -175,7 +175,7 @@ open class Menu: Button {
open override func prepare() {
super.prepare()
prepareCard()
prepareCollectionViewCard()
prepareHandler()
}
......@@ -187,8 +187,8 @@ open class Menu: Button {
}
extension Menu {
/// Prepares the card.
fileprivate func prepareCard() {
/// Prepares the collectionViewCard.
fileprivate func prepareCollectionViewCard() {
collectionViewCard.collectionView.delegate = self
collectionViewCard.collectionView.dataSource = self
collectionViewCard.collectionView.register(MenuCollectionViewCell.self, forCellWithReuseIdentifier: "MenuCollectionViewCell")
......
......@@ -181,7 +181,8 @@ open class NavigationDrawerController: RootController {
the leftView is opened, if it is below the threshold, the
leftView is closed.
*/
@IBInspectable public var leftThreshold: CGFloat = 64
@IBInspectable
open var leftThreshold: CGFloat = 64
fileprivate var leftViewThreshold: CGFloat = 0
/**
......@@ -191,7 +192,8 @@ open class NavigationDrawerController: RootController {
the rightView is closed, if it is below the threshold, the
rightView is opened.
*/
@IBInspectable public var rightThreshold: CGFloat = 64
@IBInspectable
open var rightThreshold: CGFloat = 64
fileprivate var rightViewThreshold: CGFloat = 0
/**
......@@ -505,36 +507,45 @@ open class NavigationDrawerController: RootController {
if hide {
UIView.animate(withDuration: duration,
animations: { [weak self] in
if let s = self {
v.bounds.size.width = width
v.position.x = -width / 2
s.rootViewController.view.alpha = 1
guard let s = self else {
return
}
v.bounds.size.width = width
v.position.x = -width / 2
s.rootViewController.view.alpha = 1
}) { [weak self] _ in
if let s = self {
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.hideView(container: v)
guard let s = self else {
return
}
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.hideView(container: v)
}
} else {
UIView.animate(withDuration: duration,
animations: { [weak self] in
if let s = self {
v.bounds.size.width = width
v.position.x = width / 2
s.rootViewController.view.alpha = 0.5
guard let s = self else {
return
}
v.bounds.size.width = width
v.position.x = width / 2
s.rootViewController.view.alpha = 0.5
}) { [weak self] _ in
if let s = self {
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.showView(container: v)
guard let s = self else {
return
}
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.showView(container: v)
}
}
} else {
v.bounds.size.width = width
if hide {
hideView(container: v)
v.position.x = -v.width / 2
......@@ -547,6 +558,7 @@ open class NavigationDrawerController: RootController {
rootViewController.view.alpha = 0.5
v.isShadowPathAutoSizing = true
}
layoutSubviews()
}
}
......@@ -580,36 +592,45 @@ open class NavigationDrawerController: RootController {
if hide {
UIView.animate(withDuration: duration,
animations: { [weak self] in
if let s = self {
v.bounds.size.width = width
v.position.x = s.view.bounds.width + width / 2
s.rootViewController.view.alpha = 1
guard let s = self else {
return
}
v.bounds.size.width = width
v.position.x = s.view.bounds.width + width / 2
s.rootViewController.view.alpha = 1
}) { [weak self] _ in
if let s = self {
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.hideView(container: v)
guard let s = self else {
return
}
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.hideView(container: v)
}
} else {
UIView.animate(withDuration: duration,
animations: { [weak self] in
if let s = self {
v.bounds.size.width = width
v.position.x = s.view.bounds.width - width / 2
s.rootViewController.view.alpha = 0.5
guard let s = self else {
return
}
v.bounds.size.width = width
v.position.x = s.view.bounds.width - width / 2
s.rootViewController.view.alpha = 0.5
}) { [weak self] _ in
if let s = self {
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.showView(container: v)
guard let s = self else {
return
}
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.showView(container: v)
}
}
} else {
v.bounds.size.width = width
if hide {
hideView(container: v)
v.position.x = view.bounds.width + v.width / 2
......@@ -622,6 +643,7 @@ open class NavigationDrawerController: RootController {
rootViewController.view.alpha = 0.5
v.isShadowPathAutoSizing = true
}
layoutSubviews()
}
}
......@@ -1088,15 +1110,19 @@ extension NavigationDrawerController: UIGestureRecognizerDelegate {
if !isRightViewOpened && gestureRecognizer == leftPanGesture && (isLeftViewOpened || isPointContainedWithinLeftThreshold(point: touch.location(in: view))) {
return true
}
if !isLeftViewOpened && gestureRecognizer == rightPanGesture && (isRightViewOpened || isPointContainedWithinRighThreshold(point: touch.location(in: view))) {
return true
}
if isLeftViewOpened && gestureRecognizer == leftTapGesture {
return true
}
if isRightViewOpened && gestureRecognizer == rightTapGesture {
return true
}
return false
}
......@@ -1108,47 +1134,49 @@ extension NavigationDrawerController: UIGestureRecognizerDelegate {
*/
@objc
fileprivate func handleLeftViewPanGesture(recognizer: UIPanGestureRecognizer) {
if isLeftViewEnabled && (isLeftViewOpened || !isRightViewOpened && isPointContainedWithinLeftThreshold(point: recognizer.location(in: view))) {
guard let v = leftView else {
return
guard isLeftViewEnabled && (isLeftViewOpened || !isRightViewOpened && isPointContainedWithinLeftThreshold(point: recognizer.location(in: view))) else {
return
}
guard let v = leftView else {
return
}
let point = recognizer.location(in: view)
// Animate the panel.
switch recognizer.state {
case .began:
originalX = v.position.x
showView(container: v)
delegate?.navigationDrawerController?(navigationDrawerController: self, didBeginPanAt: point, position: .left)
case .changed:
let w = v.width
let translationX = recognizer.translation(in: v).x
v.position.x = originalX + translationX > (w / 2) ? (w / 2) : originalX + translationX
let a = 1 - v.position.x / v.width
rootViewController.view.alpha = 0.5 < a && v.position.x <= v.width / 2 ? a : 0.5
if translationX >= leftThreshold {
hideStatusBar()
}
let point = recognizer.location(in: view)
delegate?.navigationDrawerController?(navigationDrawerController: self, didChangePanAt: point, position: .left)
case .ended, .cancelled, .failed:
let p = recognizer.velocity(in: recognizer.view)
let x = p.x >= 1000 || p.x <= -1000 ? p.x : 0
// Animate the panel.
switch recognizer.state {
case .began:
originalX = v.position.x
showView(container: v)
delegate?.navigationDrawerController?(navigationDrawerController: self, didBeginPanAt: point, position: .left)
case .changed:
let w = v.width
let translationX = recognizer.translation(in: v).x
v.position.x = originalX + translationX > (w / 2) ? (w / 2) : originalX + translationX
let a = 1 - v.position.x / v.width
rootViewController.view.alpha = 0.5 < a && v.position.x <= v.width / 2 ? a : 0.5
if translationX >= leftThreshold {
hideStatusBar()
}
delegate?.navigationDrawerController?(navigationDrawerController: self, didChangePanAt: point, position: .left)
case .ended, .cancelled, .failed:
let p = recognizer.velocity(in: recognizer.view)
let x = p.x >= 1000 || p.x <= -1000 ? p.x : 0
delegate?.navigationDrawerController?(navigationDrawerController: self, didEndPanAt: point, position: .left)
if v.x <= -leftViewWidth + leftViewThreshold || x < -1000 {
closeLeftView(velocity: x)
} else {
openLeftView(velocity: x)
}
case .possible:break
delegate?.navigationDrawerController?(navigationDrawerController: self, didEndPanAt: point, position: .left)
if v.x <= -leftViewWidth + leftViewThreshold || x < -1000 {
closeLeftView(velocity: x)
} else {
openLeftView(velocity: x)
}
case .possible:break
}
}
......@@ -1160,47 +1188,49 @@ extension NavigationDrawerController: UIGestureRecognizerDelegate {
*/
@objc
fileprivate func handleRightViewPanGesture(recognizer: UIPanGestureRecognizer) {
if isRightViewEnabled && (isRightViewOpened || !isLeftViewOpened && isPointContainedWithinRighThreshold(point: recognizer.location(in: view))) {
guard let v = rightView else {
return
guard isRightViewEnabled && (isRightViewOpened || !isLeftViewOpened && isPointContainedWithinRighThreshold(point: recognizer.location(in: view))) else {
return
}
guard let v = rightView else {
return
}
let point = recognizer.location(in: view)
// Animate the panel.
switch recognizer.state {
case .began:
originalX = v.position.x
showView(container: v)
delegate?.navigationDrawerController?(navigationDrawerController: self, didBeginPanAt: point, position: .right)
case .changed:
let w = v.width
let translationX = recognizer.translation(in: v).x
v.position.x = originalX + translationX < view.bounds.width - (w / 2) ? view.bounds.width - (w / 2) : originalX + translationX
let a = 1 - (view.bounds.width - v.position.x) / v.width
rootViewController.view.alpha = 0.5 < a && v.position.x >= v.width / 2 ? a : 0.5
if translationX <= -rightThreshold {
hideStatusBar()
}
let point = recognizer.location(in: view)
delegate?.navigationDrawerController?(navigationDrawerController: self, didChangePanAt: point, position: .right)
case .ended, .cancelled, .failed:
let p = recognizer.velocity(in: recognizer.view)
let x = p.x >= 1000 || p.x <= -1000 ? p.x : 0
// Animate the panel.
switch recognizer.state {
case .began:
originalX = v.position.x
showView(container: v)
delegate?.navigationDrawerController?(navigationDrawerController: self, didBeginPanAt: point, position: .right)
case .changed:
let w = v.width
let translationX = recognizer.translation(in: v).x
v.position.x = originalX + translationX < view.bounds.width - (w / 2) ? view.bounds.width - (w / 2) : originalX + translationX
let a = 1 - (view.bounds.width - v.position.x) / v.width
rootViewController.view.alpha = 0.5 < a && v.position.x >= v.width / 2 ? a : 0.5
if translationX <= -rightThreshold {
hideStatusBar()
}
delegate?.navigationDrawerController?(navigationDrawerController: self, didChangePanAt: point, position: .right)
case .ended, .cancelled, .failed:
let p = recognizer.velocity(in: recognizer.view)
let x = p.x >= 1000 || p.x <= -1000 ? p.x : 0
delegate?.navigationDrawerController?(navigationDrawerController: self, didEndPanAt: point, position: .right)
if v.x >= rightViewThreshold || x > 1000 {
closeRightView(velocity: x)
} else {
openRightView(velocity: x)
}
case .possible:break
delegate?.navigationDrawerController?(navigationDrawerController: self, didEndPanAt: point, position: .right)
if v.x >= rightViewThreshold || x > 1000 {
closeRightView(velocity: x)
} else {
openRightView(velocity: x)
}
case .possible:break
}
}
......@@ -1222,9 +1252,11 @@ extension NavigationDrawerController: UIGestureRecognizerDelegate {
delegate?.navigationDrawerController?(navigationDrawerController: self, didTapAt: recognizer.location(in: view), position: .left)
if isLeftViewEnabled && isLeftViewOpened && !isPointContainedWithinView(container: v, point: recognizer.location(in: v)) {
closeLeftView()
guard isLeftViewEnabled && isLeftViewOpened && !isPointContainedWithinView(container: v, point: recognizer.location(in: v)) else {
return
}
closeLeftView()
}
/**
......@@ -1245,8 +1277,10 @@ extension NavigationDrawerController: UIGestureRecognizerDelegate {
delegate?.navigationDrawerController?(navigationDrawerController: self, didTapAt: recognizer.location(in: view), position: .right)
if isRightViewEnabled && isRightViewOpened && !isPointContainedWithinView(container: v, point: recognizer.location(in: v)) {
closeRightView()
guard isRightViewEnabled && isRightViewOpened && !isPointContainedWithinView(container: v, point: recognizer.location(in: v)) else {
return
}
closeRightView()
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment