You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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") 
    } 
}

关键修改点说明

  1. 用速度方向替代位置临界点:通过recognizer.velocity(in: self)获取滑动速度,直接判断用户的操作方向,完全避免了临界点的限制
  2. 增加速度阈值:设置velocityThreshold是为了处理用户轻轻拖动后释放的场景,此时根据距离最近的吸附点来处理,更人性化
  3. 提取常量:把expandedY抽成单独的常量,后续修改位置时更方便维护
  4. 优化边界判断:.changed状态下的顶部边界判断,直接用self.frame.minY更直观

这样修改后,用户的滑动操作会更符合直觉——向上滑就展开,向下滑就收起,快速滑动再也不会因为没越过临界点而弹回了。

内容的提问来源于stack exchange,提问作者Niall Kiddle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:23:42