iOS 自定制手势:平移手势添加限制,方向和容错

1,661 阅读2分钟

自定义手势,一般继承自某一类系统手势。

本文的例子是,自定制右滑,仅仅右滑,方向夹角,限制在上下 20 度。必须移动 30 个像素,才触发。

手势右滑,简单一些,就是看速度

@objc func push(_ gesture: UIPanGestureRecognizer){
        if gesture.state == UIGestureRecognizer.State.began{
            let velocity = gesture.velocity(in: gesture.view)
            if velocity.x>0{
                 // ...
            }
        }
    }

要添加更多的限制,最好自定制手势

继承手势,能操作的就是几个 touches 方法,

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent)

等 3 个

继承手势并自定制的关键,是状态管理。

一般手势触发的状态,有 BeganEnded, PosssibleCancle 不触发。

自定制手势的内部状态一 Ended, 外部的方法,就会触发。

直接重写,状态全部自己来管理。

如果调用父类的实现,super.touches, 状态就会多一些系统的实现。

例如,仅仅在


override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
    super.touchesMoved(touches, with: event)
}

state 会从 possible , 到 began, 再到 changed

可以通过 state 的 rawValue 来查看

剩下的,就是条件判断了。

有了什么样的条件,就会决定什么样的状态。

下面的代码,是通过判断坐标,方便做容错。

以免用户手指想点击,操作成平移,把界面移没了。

一般情况下,可以用速度,不用坐标


源代码:

struct Constaint {
    let maxAngle: Double
    let minSpeed: CGFloat
    let toleranceDistance:CGFloat
    
    static let `default` = Constaint(maxAngle: 20, minSpeed: 40, toleranceDistance: 30)
}


class RightPanGesture: UIPanGestureRecognizer {

      var curTickleStart: CGPoint?

      let constraint: Constaint
      
      
      init(target: AnyObject, action: Selector, constraint limits: Constaint = Constaint.default) {
        
          constraint = limits
          super.init(target: target, action: action)
      }

      override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
             if let touch = touches.first {
                 curTickleStart = touch.location(in: view)
             }
       }
     
    
    
      override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
             if let start = curTickleStart, let touch = touches.first{
                    let ticklePoint = touch.location(in: view)
                    let moveAmt = ticklePoint.x - start.x
                    guard moveAmt > constraint.toleranceDistance else{
                        return
                    }
                    let tangent = tan(constraint.maxAngle * Double.pi / 180)
                    if abs(ticklePoint.y - start.y)/abs(moveAmt) > CGFloat(tangent){
                        state = .cancelled
                    }
                    if state == .possible{
                        state = .ended
                    }
              }
      }
      
      override func reset() {
        curTickleStart = nil
        if state == .possible {
           state = .failed
        }
      }
      
      override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
          reset()
      }
      
      override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
          reset()
      }

  
}

调用:



class SidePanView: UIView{
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        let pan = RightPanGesture(target: self, action: #selector(SidePanView.push(_:)))
        addGestureRecognizer(pan)
        isUserInteractionEnabled = true
    }

    
    
    @objc func push(_ gesture: RightPanGesture){
        // ...
    }

}