Fix detent snapping
This commit is contained in:
parent
481f40519c
commit
3dc36d98c1
|
@ -14,13 +14,17 @@ public class SheetContainerViewController: UIViewController {
|
|||
|
||||
public var detents: [Detent] = [.bottom, .middle, .top] {
|
||||
didSet {
|
||||
|
||||
sortDetents()
|
||||
}
|
||||
}
|
||||
var sortedDetentOffsets: [CGFloat] = []
|
||||
|
||||
var topConstraint: NSLayoutConstraint!
|
||||
lazy var initialConstant: CGFloat = view.bounds.height / 2
|
||||
|
||||
public var minimumDetentJumpVelocity: CGFloat = 500
|
||||
public var maximumStretchDistance: CGFloat = 15
|
||||
|
||||
public init(content: UIViewController) {
|
||||
self.content = content
|
||||
|
||||
|
@ -49,6 +53,18 @@ public class SheetContainerViewController: UIViewController {
|
|||
content.view.addGestureRecognizer(panGesture)
|
||||
}
|
||||
|
||||
public override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
sortDetents()
|
||||
}
|
||||
|
||||
private func sortDetents() {
|
||||
sortedDetentOffsets = detents.map {
|
||||
$0.offset(in: view)
|
||||
}.sorted()
|
||||
}
|
||||
|
||||
@objc func panGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
|
@ -57,31 +73,62 @@ public class SheetContainerViewController: UIViewController {
|
|||
case .changed:
|
||||
let translation = recognizer.translation(in: content.view)
|
||||
var realOffset = initialConstant + translation.y
|
||||
if realOffset < view.safeAreaInsets.top {
|
||||
print(realOffset)
|
||||
realOffset = view.safeAreaInsets.top - realOffset / pow(CGFloat(M_E), realOffset / view.safeAreaInsets.top)
|
||||
if realOffset < sortedDetentOffsets.first! {
|
||||
func clamp(_ value: CGFloat, from: CGFloat, to: CGFloat) -> CGFloat {
|
||||
if value < from {
|
||||
return from
|
||||
} else if value > to {
|
||||
return to
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
func smoothstep(value: CGFloat, from: CGFloat, to: CGFloat) -> CGFloat {
|
||||
let x = clamp((value - from) / (to - from), from: 0, to: 1)
|
||||
// 3x^2 - 2x^3
|
||||
return 3 * pow(x, 2) - 2 * pow(x, 3)
|
||||
}
|
||||
let topOffset = sortedDetentOffsets.first!
|
||||
let smoothed = smoothstep(value: realOffset, from: topOffset, to: 0)
|
||||
realOffset = topOffset - smoothed * maximumStretchDistance
|
||||
|
||||
}
|
||||
topConstraint.constant = realOffset
|
||||
|
||||
case .ended:
|
||||
if let offset = nearestDetentOffset(offset: topConstraint.constant) {
|
||||
let distance = abs(topConstraint.constant - offset)
|
||||
self.topConstraint.constant = offset
|
||||
let velocity = recognizer.velocity(in: view)
|
||||
let springVelocity = velocity.y / distance
|
||||
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: springVelocity, animations: {
|
||||
self.view.layoutIfNeeded()
|
||||
})
|
||||
|
||||
let springToDetent: CGFloat
|
||||
if abs(velocity.y) > minimumDetentJumpVelocity,
|
||||
let offsetInVelocityDirection = nearestDetentOffset(currentOffset: topConstraint.constant, direction: velocity.y) {
|
||||
springToDetent = offsetInVelocityDirection
|
||||
} else if let nearestOffset = nearestDetentOffset(currentOffset: topConstraint.constant) {
|
||||
springToDetent = nearestOffset
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
let springDistance = abs(topConstraint.constant - springToDetent)
|
||||
self.topConstraint.constant = springToDetent
|
||||
let springVelocity = velocity.y / springDistance
|
||||
|
||||
UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: springVelocity, animations: {
|
||||
self.view.layoutIfNeeded()
|
||||
})
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func nearestDetentOffset(offset: CGFloat) -> CGFloat? {
|
||||
return detents.map { $0.offset(in: view) }.min { (a, b) -> Bool in
|
||||
return abs(offset - a) < abs(offset - b)
|
||||
func nearestDetentOffset(currentOffset: CGFloat) -> CGFloat? {
|
||||
return sortedDetentOffsets.min(by: { abs($0 - currentOffset) < abs($1 - currentOffset) })
|
||||
}
|
||||
|
||||
func nearestDetentOffset(currentOffset: CGFloat, direction: CGFloat) -> CGFloat? {
|
||||
if direction < 0 {
|
||||
return sortedDetentOffsets.last(where: { $0 < currentOffset })
|
||||
} else {
|
||||
return sortedDetentOffsets.first(where: { $0 > currentOffset })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue