UIView Y轴平移手势优化:按滑动方向实现双位置吸附
解决UIView拖拽吸附的体验问题:基于滑动方向替代临界点判断
我完全懂你的困扰——这种依赖固定临界点的吸附逻辑太生硬了,用户快速滑动时哪怕只是差一点没越过那个中点,视图就弹回去,完全不符合用户的操作意图。咱们换个思路,**根据用户释放时的滑动方向(也就是手势的速度方向)**来决定吸附位置,这样更贴合直觉。
核心思路
在手势结束(.ended)时,获取Pan手势的Y轴速度:
- 如果速度为负(
velocity.y < 0),说明用户是向上滑动,吸附到上方的展开位置(y=80) - 如果速度为正(
velocity.y > 0),说明用户是向下滑动,吸附到下方的原始位置(partialY) - 额外处理速度接近0的情况(比如轻轻拖动后释放),此时根据当前位置更靠近哪个吸附点来决定,避免误操作
修改后的代码
@objc func panGesture(recognizer: UIPanGestureRecognizer) { let window = UIApplication.shared.keyWindow let translation = recognizer.translation(in: self) let currentY = self.frame.minY let partialY = (window?.frame.height)! - 194 let expandedY: CGFloat = 80 // 把展开位置抽成常量,更易维护 // 更新视图位置 self.frame = CGRect(x: 0, y: currentY + translation.y, width: self.frame.width, height: self.frame.height) recognizer.setTranslation(CGPoint.zero, in: self) switch recognizer.state { case .changed: // 限制顶部边界,不能滑出屏幕上方 if self.frame.minY <= 0 { self.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height) } case .ended: // 获取Y轴滑动速度,判断方向 let velocity = recognizer.velocity(in: self) let velocityY = velocity.y // 定义一个速度阈值,处理几乎没滑动的情况 let velocityThreshold: CGFloat = 100 if velocityY < -velocityThreshold { // 向上滑动,吸附到展开位置 UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.62, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: { recognizer.view!.frame = CGRect(x: 0, y: expandedY, width: self.frame.width, height: self.frame.height) }, completion: { (_) in self.isExpanded = true }) } else if velocityY > velocityThreshold { // 向下滑动,吸附到原始位置 UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.62, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: { recognizer.view!.frame = CGRect(x: 0, y: partialY, width: self.frame.width, height: self.frame.height) }, completion: { (_) in self.isExpanded = false }) } else { // 滑动速度很小,根据当前位置靠近哪个点来吸附 let distanceToExpanded = abs(currentY - expandedY) let distanceToPartial = abs(currentY - partialY) if distanceToExpanded < distanceToPartial { // 更靠近展开位置,吸附上去 UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.62, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: { recognizer.view!.frame = CGRect(x: 0, y: expandedY, width: self.frame.width, height: self.frame.height) }, completion: { (_) in self.isExpanded = true }) } else { // 更靠近原始位置,吸附回去 UIView.animate(withDuration:0.62, delay: 0.0, usingSpringWithDamping: 0.62, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: { recognizer.view!.frame = CGRect(x: 0, y: partialY, width: self.frame.width, height: self.frame.height) }, completion: { (_) in self.isExpanded = false }) } } default: print("Default") } }
关键修改点说明
- 用速度方向替代位置临界点:通过
recognizer.velocity(in: self)获取滑动速度,直接判断用户的操作方向,完全避免了临界点的限制 - 增加速度阈值:设置
velocityThreshold是为了处理用户轻轻拖动后释放的场景,此时根据距离最近的吸附点来处理,更人性化 - 提取常量:把
expandedY抽成单独的常量,后续修改位置时更方便维护 - 优化边界判断:
.changed状态下的顶部边界判断,直接用self.frame.minY更直观
这样修改后,用户的滑动操作会更符合直觉——向上滑就展开,向下滑就收起,快速滑动再也不会因为没越过临界点而弹回了。
内容的提问来源于stack exchange,提问作者Niall Kiddle
相关产品推荐
相关产品推荐

