Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Presentr/Presentr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ public enum PresentrConstants {

public enum DismissSwipeDirection {
case `default`
case bottom
case top
case bottom
case left
case right
}

/// The action that should happen when the background is tapped.
Expand Down Expand Up @@ -86,9 +88,15 @@ public class Presentr: NSObject {

/// If dismissOnSwipe is true, the direction for the swipe. Default depends on presentation type.
public var dismissOnSwipeDirection: DismissSwipeDirection = .default

/// Should the presented controller be dismissed when the gesture is ended (e.g. the user lifts their finger from the screen). Default is false
public var dismissOnRelease = false

/// Should the presented controller use animation when dismiss on background tap or swipe. Default is true.
public var dismissAnimated = true

/// How much the presented controller should resist being dragged in the opposite direction (1 is max, 0 is no resistance).
public var overdragResistanceFactor: Float?

/// Color of the background. Default is Black.
public var backgroundColor = UIColor.black
Expand Down Expand Up @@ -185,6 +193,8 @@ extension Presentr: UIViewControllerTransitioningDelegate {
backgroundTap: backgroundTap,
dismissOnSwipe: dismissOnSwipe,
dismissOnSwipeDirection: dismissOnSwipeDirection,
dismissOnRelease: dismissOnRelease,
overdragResistanceFactor: overdragResistanceFactor,
backgroundColor: backgroundColor,
backgroundOpacity: backgroundOpacity,
blurBackground: blurBackground,
Expand Down
125 changes: 93 additions & 32 deletions Presentr/PresentrController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ class PresentrController: UIPresentationController, UIAdaptivePresentationContro
/// DismissSwipe direction
let dismissOnSwipeDirection: DismissSwipeDirection

/// Should the presented controller dismiss on gesture release
let dismissOnRelease: Bool

/// Amount of overdrag resistance
let overdragResistanceFactor: Float?

/// Should the presented controller use animation when dismiss on background tap.
let dismissAnimated: Bool

Expand Down Expand Up @@ -82,18 +88,6 @@ class PresentrController: UIPresentationController, UIAdaptivePresentationContro

fileprivate var presentedViewCenter: CGPoint = .zero

fileprivate var latestShouldDismiss: Bool = true

fileprivate lazy var shouldSwipeBottom: Bool = {
let defaultDirection = dismissOnSwipeDirection == .default
return defaultDirection ? presentationType != .topHalf : dismissOnSwipeDirection == .bottom
}()

fileprivate lazy var shouldSwipeTop: Bool = {
let defaultDirection = dismissOnSwipeDirection == .default
return defaultDirection ? presentationType == .topHalf : dismissOnSwipeDirection == .top
}()

// MARK: - Init

init(presentedViewController: UIViewController,
Expand All @@ -105,6 +99,8 @@ class PresentrController: UIPresentationController, UIAdaptivePresentationContro
backgroundTap: BackgroundTapAction,
dismissOnSwipe: Bool,
dismissOnSwipeDirection: DismissSwipeDirection,
dismissOnRelease: Bool,
overdragResistanceFactor: Float?,
backgroundColor: UIColor,
backgroundOpacity: Float,
blurBackground: Bool,
Expand All @@ -119,6 +115,8 @@ class PresentrController: UIPresentationController, UIAdaptivePresentationContro
self.backgroundTap = backgroundTap
self.dismissOnSwipe = dismissOnSwipe
self.dismissOnSwipeDirection = dismissOnSwipeDirection
self.dismissOnRelease = dismissOnRelease
self.overdragResistanceFactor = overdragResistanceFactor
self.keyboardTranslationType = keyboardTranslationType
self.dismissAnimated = dismissAnimated
self.contextFrameForPresentation = contextFrameForPresentation
Expand Down Expand Up @@ -417,15 +415,10 @@ extension PresentrController {
if gesture.state == .began {
presentedViewFrame = presentedViewController.view.frame
presentedViewCenter = presentedViewController.view.center

let directionDown = gesture.translation(in: presentedViewController.view).y > 0
if (shouldSwipeBottom && directionDown) || (shouldSwipeTop && !directionDown) {
latestShouldDismiss = conformingPresentedController?.presentrShouldDismiss?(keyboardShowing: keyboardIsShowing) ?? true
}
} else if gesture.state == .changed {
swipeGestureChanged(gesture: gesture)
} else if gesture.state == .ended || gesture.state == .cancelled {
swipeGestureEnded()
swipeGestureEnded(gesture: gesture)
}
}

Expand All @@ -434,31 +427,61 @@ extension PresentrController {
func swipeGestureChanged(gesture: UIPanGestureRecognizer) {
let amount = gesture.translation(in: presentedViewController.view)

if shouldSwipeTop && amount.y > 0 {
return
} else if shouldSwipeBottom && amount.y < 0 {
return
var allowRender: Bool = true
var overdragResistance: CGFloat = 1

switch dismissOnSwipeDirection {
case .top:
allowRender = amount.y < 0
case .bottom:
allowRender = amount.y > 0
case .left:
allowRender = amount.x < 0
case .right:
allowRender = amount.x > 0
case .default:
break
}

var swipeLimit: CGFloat = 100
if shouldSwipeTop {
swipeLimit = -swipeLimit

if let overdragResistanceFactor = overdragResistanceFactor {
if !allowRender {
overdragResistance = 1 - .init(overdragResistanceFactor)
}

allowRender = true
}

presentedViewController.view.center = CGPoint(x: presentedViewCenter.x, y: presentedViewCenter.y + amount.y)

let dismiss = shouldSwipeTop ? (amount.y < swipeLimit) : ( amount.y > swipeLimit)
if dismiss && latestShouldDismiss {
guard allowRender else {
return
}

switch dismissOnSwipeDirection {
case .top, .bottom:
presentedViewController.view.center = CGPoint(x: presentedViewCenter.x, y: presentedViewCenter.y + (amount.y * overdragResistance))
case .left, .right:
presentedViewController.view.center = CGPoint(x: presentedViewCenter.x + (amount.x * overdragResistance), y: presentedViewCenter.y)
default:
break
}

if !dismissOnRelease && shouldDismiss(gesture: gesture) {
presentedViewIsBeingDissmissed = true
presentedViewController.dismiss(animated: dismissAnimated, completion: nil)
}
}

func swipeGestureEnded() {
func swipeGestureEnded(gesture: UIPanGestureRecognizer) {
guard !presentedViewIsBeingDissmissed else {
return
}


if dismissOnRelease && shouldDismiss(gesture: gesture) {
presentedViewIsBeingDissmissed = true
presentedViewController.dismiss(animated: dismissAnimated, completion: nil)

return
}

UIView.animate(withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 0.5,
Expand All @@ -468,6 +491,44 @@ extension PresentrController {
self.presentedViewController.view.frame = self.presentedViewFrame
}, completion: nil)
}

private func shouldDismiss(gesture: UIPanGestureRecognizer) -> Bool {
let amount = gesture.translation(in: presentedViewController.view)
let velocity = gesture.velocity(in: presentedViewController.view)

var shouldDismiss = false

switch dismissOnSwipeDirection {
case .top:
if amount.y > 0 {
break
}

shouldDismiss = -velocity.y > 1000 || -amount.y > (presentedViewFrame.height / 2)
case .bottom:
if amount.y < 0 {
break
}

shouldDismiss = velocity.y > 1000 || amount.y > (presentedViewFrame.height / 2)
case .left:
if amount.x > 0 {
break
}

shouldDismiss = -velocity.x > 1000 || -amount.x > (presentedViewFrame.width / 2)
case .right:
if amount.x < 0 {
break
}

shouldDismiss = velocity.x > 1000 || amount.x > (presentedViewFrame.width / 2)
case .default:
break
}

return shouldDismiss
}

}

Expand Down