You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DragGestureController.swift 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /*
  2. * Copyright @ 2017-present Atlassian Pty Ltd
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. final class DragGestureController {
  17. var insets: UIEdgeInsets = UIEdgeInsets.zero
  18. private var frameBeforeDragging: CGRect = CGRect.zero
  19. private weak var view: UIView?
  20. private lazy var panGesture: UIPanGestureRecognizer = {
  21. return UIPanGestureRecognizer(target: self,
  22. action: #selector(handlePan(gesture:)))
  23. }()
  24. func startDragListener(inView view: UIView) {
  25. self.view = view
  26. view.addGestureRecognizer(panGesture)
  27. panGesture.isEnabled = true
  28. }
  29. func stopDragListener() {
  30. panGesture.isEnabled = false
  31. view?.removeGestureRecognizer(panGesture)
  32. view = nil
  33. }
  34. @objc private func handlePan(gesture: UIPanGestureRecognizer) {
  35. guard let view = self.view else { return }
  36. let translation = gesture.translation(in: view.superview)
  37. let velocity = gesture.velocity(in: view.superview)
  38. var frame = frameBeforeDragging
  39. switch gesture.state {
  40. case .began:
  41. frameBeforeDragging = view.frame
  42. case .changed:
  43. frame.origin.x = floor(frame.origin.x + translation.x)
  44. frame.origin.y = floor(frame.origin.y + translation.y)
  45. view.frame = frame
  46. case .ended:
  47. let currentPos = view.frame.origin
  48. let finalPos = calculateFinalPosition()
  49. let distance = CGPoint(x: currentPos.x - finalPos.x,
  50. y: currentPos.y - finalPos.y)
  51. let distanceMagnitude = magnitude(vector: distance)
  52. let velocityMagnitude = magnitude(vector: velocity)
  53. let animationDuration = 0.5
  54. let initialSpringVelocity =
  55. velocityMagnitude / distanceMagnitude / CGFloat(animationDuration)
  56. frame.origin = CGPoint(x: finalPos.x, y: finalPos.y)
  57. UIView.animate(withDuration: animationDuration,
  58. delay: 0,
  59. usingSpringWithDamping: 0.9,
  60. initialSpringVelocity: initialSpringVelocity,
  61. options: .curveLinear,
  62. animations: {
  63. view.frame = frame
  64. }, completion: nil)
  65. default:
  66. break
  67. }
  68. }
  69. private func calculateFinalPosition() -> CGPoint {
  70. guard
  71. let view = self.view,
  72. let bounds = view.superview?.frame
  73. else { return CGPoint.zero }
  74. let currentSize = view.frame.size
  75. let adjustedBounds = UIEdgeInsetsInsetRect(bounds, insets)
  76. let threshold: CGFloat = 20.0
  77. let velocity = panGesture.velocity(in: view.superview)
  78. let location = panGesture.location(in: view.superview)
  79. let goLeft: Bool
  80. if fabs(velocity.x) > threshold {
  81. goLeft = velocity.x < -threshold
  82. } else {
  83. goLeft = location.x < bounds.midX
  84. }
  85. let goUp: Bool
  86. if fabs(velocity.y) > threshold {
  87. goUp = velocity.y < -threshold
  88. } else {
  89. goUp = location.y < bounds.midY
  90. }
  91. let finalPosX: CGFloat =
  92. goLeft
  93. ? adjustedBounds.origin.x
  94. : bounds.size.width - insets.right - currentSize.width
  95. let finalPosY: CGFloat =
  96. goUp
  97. ? adjustedBounds.origin.y
  98. : bounds.size.height - insets.bottom - currentSize.height
  99. return CGPoint(x: finalPosX, y: finalPosY)
  100. }
  101. private func magnitude(vector: CGPoint) -> CGFloat {
  102. return sqrt(pow(vector.x, 2) + pow(vector.y, 2))
  103. }
  104. }