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

Swift中Method Swizzling未调用UIViewController原touchesBegan/Ended方法

问题:Swift中Method Swizzling拦截UIResponder触摸方法后,UIViewController原方法未触发

尝试通过Method Swizzling拦截所有UIResponder实例的touchesBegan和touchesEnded方法,但发现UIViewController中重写的原始触摸方法没有被调用。使用的代码如下:

extension UIResponder {
    static let swizzleTouchMethods: Void = {
        let originalBeganSelector = #selector(touchesBegan(_:with:))
        let swizzledBeganSelector = #selector(xxx_touchesBegan(_:with:))

        guard let originalBeganMethod = class_getInstanceMethod(UIView.self, originalBeganSelector),
              let swizzledBeganMethod = class_getInstanceMethod(UIView.self, swizzledBeganSelector) else {
            return
        }

        method_exchangeImplementations(originalBeganMethod, swizzledBeganMethod)

        let originalMovedSelector = #selector(touchesMoved(_:with:))
        let swizzledMovedSelector = #selector(xxx_touchesMoved(_:with:))

        guard let originalMovedMethod = class_getInstanceMethod(UIView.self, originalMovedSelector),
              let swizzledMovedMethod = class_getInstanceMethod(UIView.self, swizzledMovedSelector) else {
            return
        }

        method_exchangeImplementations(originalMovedMethod, swizzledMovedMethod)

        let originalEndedSelector = #selector(touchesEnded(_:with:))
        let swizzledEndedSelector = #selector(xxx_touchesEnded(_:with:))

        guard let originalEndedMethod = class_getInstanceMethod(UIView.self, originalEndedSelector),
              let swizzledEndedMethod = class_getInstanceMethod(UIView.self, swizzledEndedSelector) else {
            return
        }

        method_exchangeImplementations(originalEndedMethod, swizzledEndedMethod)
    }()

    @objc func xxx_touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            print("Swizzled touch began at: radius \(touch.majorRadius)")
        }

        // Call the original method (which is now swizzled)
        self.xxx_touchesBegan(touches, with: event)
    }

    @objc func xxx_touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            print("Swizzled touch moved at: radius \(touch.majorRadius)")
        }

        // Call the original method (which is now swizzled)
        self.xxx_touchesMoved(touches, with: event)
    }

    @objc func xxx_touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            print("Swizzled touch ended at: radius \(touch.majorRadius)")
        }

        // Call the original method (which is now swizzled)
        self.xxx_touchesEnded(touches, with: event)
    }
}

final class MyViewController: UIViewController { 

  override func viewDidLoad() {
     super.viewDidLoad()
     UIResponder.swizzleTouchMethods
  }

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

 override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesEnded(touches, with: event)
    print("original touchesEnded event")
  
  }
}

问题原因

  1. 错误的Swizzle目标类:当前代码交换的是UIView类的触摸方法,而UIViewController是UIResponder的直接子类,它自身重写了touchesBegan/touchesEnded方法,事件响应链中会优先调用VC的这些方法,但你的Swizzle操作并未影响UIViewController类的方法实现。
  2. Swizzle逻辑不完整:未处理子类未重写父类方法的情况,直接交换可能导致部分场景失效。

解决方案

修正后的代码

import UIKit

extension UIResponder {
    static let swizzleTouchMethods: Void = {
        // 针对UIResponder类进行方法交换,覆盖所有子类
        guard let cls = UIResponder.self as? AnyClass else { return }
        
        // 处理touchesBegan交换
        let originalBeganSelector = #selector(touchesBegan(_:with:))
        let swizzledBeganSelector = #selector(swizzled_touchesBegan(_:with:))
        
        guard let originalMethod = class_getInstanceMethod(cls, originalBeganSelector),
              let swizzledMethod = class_getInstanceMethod(cls, swizzledBeganSelector) else {
            return
        }
        
        // 先尝试添加原方法实现,避免子类未重写时交换失效
        let didAddBeganMethod = class_addMethod(cls, originalBeganSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod))
        
        if didAddBeganMethod {
            class_replaceMethod(cls, swizzledBeganSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
        
        // 处理touchesEnded交换
        let originalEndedSelector = #selector(touchesEnded(_:with:))
        let swizzledEndedSelector = #selector(swizzled_touchesEnded(_:with:))
        
        guard let originalEndedMethod = class_getInstanceMethod(cls, originalEndedSelector),
              let swizzledEndedMethod = class_getInstanceMethod(cls, swizzledEndedSelector) else {
            return
        }
        
        let didAddEndedMethod = class_addMethod(cls, originalEndedSelector,
                                                method_getImplementation(swizzledEndedMethod),
                                                method_getTypeEncoding(swizzledEndedMethod))
        
        if didAddEndedMethod {
            class_replaceMethod(cls, swizzledEndedSelector,
                                method_getImplementation(originalEndedMethod),
                                method_getTypeEncoding(originalEndedMethod))
        } else {
            method_exchangeImplementations(originalEndedMethod, swizzledEndedMethod)
        }
        
        // 处理touchesMoved交换
        let originalMovedSelector = #selector(touchesMoved(_:with:))
        let swizzledMovedSelector = #selector(swizzled_touchesMoved(_:with:))
        
        guard let originalMovedMethod = class_getInstanceMethod(cls, originalMovedSelector),
              let swizzledMovedMethod = class_getInstanceMethod(cls, swizzledMovedSelector) else {
            return
        }
        
        let didAddMovedMethod = class_addMethod(cls, originalMovedSelector,
                                                method_getImplementation(swizzledMovedMethod),
                                                method_getTypeEncoding(swizzledMovedMethod))
        
        if didAddMovedMethod {
            class_replaceMethod(cls, swizzledMovedSelector,
                                method_getImplementation(originalMovedMethod),
                                method_getTypeEncoding(originalMovedMethod))
        } else {
            method_exchangeImplementations(originalMovedMethod, swizzledMovedMethod)
        }
    }()
    
    @objc private func swizzled_touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            print("Swizzled touch began at: radius \(touch.majorRadius)")
        }
        // 交换后,调用swizzled方法等价于调用原方法
        self.swizzled_touchesBegan(touches, with: event)
    }
    
    @objc private func swizzled_touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            print("Swizzled touch ended at: radius \(touch.majorRadius)")
        }
        self.swizzled_touchesEnded(touches, with: event)
    }
    
    @objc private func swizzled_touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            print("Swizzled touch moved at: radius \(touch.majorRadius)")
        }
        self.swizzled_touchesMoved(touches, with: event)
    }
}

final class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 建议将Swizzle放在App启动时执行一次,比如AppDelegate中
        UIResponder.swizzleTouchMethods
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        print("original touchesBegan event")
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        print("original touchesEnded event")
    }
}

关键修正点

  1. 针对UIResponder类交换:确保所有UIResponder子类(包括UIViewController、UIView等)的触摸方法都会被拦截。
  2. 完善方法交换逻辑:通过class_addMethod处理子类未重写父类方法的场景,避免交换失效。
  3. 优化Swizzle时机:建议在App启动时(如AppDelegate.application(_:didFinishLaunchingWithOptions:))执行一次,避免重复交换。

内容的提问来源于stack exchange,提问作者Sajal Gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 10:25:07