【问题标题】:Swizzling UIResponder Touch events not invoking original method implementationSwizzling UIResponder Touch 事件不调用原始方法实现
【发布时间】:2018-12-17 15:56:50
【问题描述】:

我正在尝试通过创建 UIResponder 扩展来调整 UITouch 生命周期事件。以下是描述touchesBegan 实现的摘录。

 public extension UIResponder {

   private enum TouchPhase: String {
    case begin
    case move
    case end
    case cancel
    //computed property
    var phase: String {
        return self.rawValue
    }
}

    @objc func swizztouchesBegan(_ touches: Set<UITouch>, with event: 
            UIEvent?) {
         print("swizztouchesBegan event called!")
         let touch = touches.first! as UITouch
         self.createTouchesDict(for: touch, event: event, phase: .begin)
         self.swizztouchesBegan(touches, with: event)
     }

    static func swizzleTouchesBegan() {
       let _: () = {
            let originalSelector = 
        #selector(UIResponder.touchesBegan(_:with:))
            let swizzledSelector = 
        #selector(UIResponder.swizztouchesBegan(_:with:))
             let originalMethod = class_getInstanceMethod(UIResponder.self, originalSelector)
             let swizzledMethod = class_getInstanceMethod(UIResponder.self, swizzledSelector)
    method_exchangeImplementations(originalMethod!, swizzledMethod!)
    }()
 }
}

然后我会像这样在我的应用程序中调用它-

 [UIResponder swizzleTouchesBegan]; //my app is a mix of Objective-C and Swift

我看到的是-“调用了 swizztouchesBegan 事件!”被打印出来,我开始创建触摸元数据的字典。但是,它看起来并没有调用原始实现,例如假设在我的代码中的某个地方,在表格视图控制器中,我编写了以下内容-

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    print("AM I GETTING CALLED?")
} 

这个被覆盖的 touchesBegan 没有被调用 - 可能是什么问题?我已经通过再次调用 swizzled 来从 swizzled 内部调用原始实现(从而确保调用原始实现。)

PS/ 附录:我已经能够通过覆盖 UIView 扩展中的触摸方法来使其工作,如下所示。有人能指出使用这种方法是否有任何劣势吗?

public extension UIView {

private enum TouchPhase: String {
    case begin
    case move
    case end
    case cancel
    //computed property
    var phase: String {
        return self.rawValue
    }
}
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    print("overriding touches began event!")
    let touch = touches.first! as UITouch
    self.createTouchesDict(for: touch, event: event, phase: .begin)
    super.touchesBegan(touches, with: event)
}

open override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("overriding touches moved event!")
    let touch = touches.first! as UITouch
    self.createTouchesDict(for: touch, event: event, phase: .move)
    super.touchesMoved(touches, with: event)

}

open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("overriding touches ended event!")
    let touch = touches.first! as UITouch
    self.createTouchesDict(for: touch, event: event, phase: .end)
    super.touchesEnded(touches, with: event)

}

  open override func touchesCancelled(_ touches: Set<UITouch>, with 
 event: UIEvent?) {
    print("overriding touches canceled event!")
    let touch = touches.first! as UITouch
    self.createTouchesDict(for: touch, event: event, phase: .cancel)
    super.touchesCancelled(touches, with: event)

    }
  }

【问题讨论】:

    标签: ios swift uitouch objective-c-runtime method-swizzling


    【解决方案1】:

    似乎 swift 不需要方法 swizzle,因为有扩展。

    因此,如果您想为任何课程调配 touch,您只需覆盖 touch。如下所示,我想为所有 Bounce 赋予反弹效果

    extension UIView {
        func highlight(color: UIColor) {
            let anim = CABasicAnimation.init(keyPath: #keyPath(CALayer.backgroundColor))
            anim.fromValue = UIColor.clear.cgColor
            anim.toValue = color.cgColor
            anim.duration = 0.5
            anim.autoreverses = true
            anim.repeatCount = 0
            anim.fillMode = kCAFillModeForwards
            anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    
            self.layer.add(anim, forKey: "blink")
        }
    
        func bounce() {
            let endTransform = CGAffineTransform(scaleX: 1.0, y: 1.0)
            let prepareTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
            UIView.animateKeyframes(
                withDuration: 0.2, delay: 0.0, options: UIView.KeyframeAnimationOptions.calculationModeLinear, animations: {
                    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.1) {
                        self.transform = prepareTransform
                    }
                    UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.1) {
                        self.transform = endTransform
                    }
            }, completion: nil)
        }
    }
    
    extension BounceButton {
        open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            self.bounce()
            super.touchesBegan(touches, with: event)
        }
    }
    

    但在你的情况下,你可以为所有 UIView 扩展做,所以一切都可以反弹。

    【讨论】:

      【解决方案2】:

      在我看来,您总是在调整选择器而不检查是否已经完成。

      这里有一个关于如何做到这一点的示例。

      extension UIResponder {
      
          static let swizzleIfNeeded: () = {
              swizzle(originalSelector: #selector(touchesBegan(_:with:)), to: #selector(swizztouchesBegan(_:with:)))
          }()
      
          static func swizzle(originalSelector: Selector, to swizzledSelector: Selector) {
              let originalMethod = class_getInstanceMethod(self, originalSelector)
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
              let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
              if didAddMethod {
                  class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
              } else {
                  method_exchangeImplementations(originalMethod!, swizzledMethod!);
              }
          }
      
          @objc func swizztouchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
              print("swizztouchesBegan event called!")
              let touch = touches.first! as UITouch
              self.createTouchesDict(for: touch, event: event, phase: .begin)
              self.swizztouchesBegan(touches, with: event)
          }
      }
      

      然后在您的应用程序中,委托确实完成了(例如)

      UIResponder.swizzleIfNeeded
      

      【讨论】:

      • 我尝试了您的解决方案 - 我认为这是一个优化的解决方案,但它仍然没有解决问题。原始的 impl 仍未被调用。因此,假设我点击 UICollectionView 中的一个单元格,它的 didSelectItemAt 委托没有被调用。不知道为什么会这样。
      猜你喜欢
      • 2017-01-26
      • 1970-01-01
      • 1970-01-01
      • 2020-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-11
      相关资源
      最近更新 更多