Swift中已子类化WKWebView,如何替换其内置UIScrollView为自定义子类?
替换WKWebView内置UIScrollView为自定义子类的可行方案
嘿,这个问题我之前也琢磨过——确实Apple把WKWebView的scrollView设成只读,直接赋值肯定行不通。先给你明确一点:直接替换WebKit内置的UIScrollView并不是官方支持的操作,强行搞的话可能会触发WebView内部逻辑异常,甚至过不了App Store审核。不过如果确实有刚需,下面两种思路可以试试:
方法1:用Runtime Hook替换ScrollView创建逻辑(风险较高)
WKWebView初始化时会调用私有方法_createScrollView来创建内置的滚动视图,我们可以利用Objective-C Runtime的方法交换,让它返回我们的自定义子类实例。
先定义你的自定义ScrollView:
class CustomScrollView: UIScrollView { // 这里加你的自定义逻辑,比如拦截触摸事件 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) print("自定义滚动视图捕获触摸事件") } }
然后在自定义WKWebView里做方法交换:
import WebKit import ObjectiveC class CustomWKWebView: WKWebView { override init(frame: CGRect, configuration: WKWebViewConfiguration) { super.init(frame: frame, configuration: configuration) swizzleScrollViewCreation() } required init?(coder: NSCoder) { super.init(coder: coder) swizzleScrollViewCreation() } private func swizzleScrollViewCreation() { let originalSelector = Selector("_createScrollView") let swizzledSelector = #selector(swizzled_createScrollView) guard let originalMethod = class_getInstanceMethod(WKWebView.self, originalSelector), let swizzledMethod = class_getInstanceMethod(Self.self, swizzledSelector) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) } @objc private func swizzled_createScrollView() -> UIScrollView { // 返回我们的自定义滚动视图 return CustomScrollView() } }
⚠️ 重要提醒:这里用到了WebKit的私有方法_createScrollView,Apple随时可能在iOS新版本里修改或移除它,导致代码失效。而且用私有API有很大概率被App Store审核拒绝,这个方案只适合内部测试或者非上架的场景。
方法2:通过代理注入自定义逻辑(安全稳定)
如果不想碰私有API,更稳妥的方式是不替换内置ScrollView,而是给它加自定义代理逻辑,这样完全用公开API,没有审核风险,也更稳定。
比如,我们可以包装一个自定义代理,接管需要修改的滚动/触摸逻辑,同时把其他方法转发给原始代理:
class CustomScrollDelegate: NSObject, UIScrollViewDelegate { private let originalDelegate: UIScrollViewDelegate? init(originalDelegate: UIScrollViewDelegate?) { self.originalDelegate = originalDelegate super.init() } // 覆盖你需要自定义的代理方法 func scrollViewDidScroll(_ scrollView: UIScrollView) { // 先调用原始代理的逻辑 originalDelegate?.scrollViewDidScroll?(scrollView) // 再添加你的自定义逻辑 print("滚动事件触发,执行自定义处理") } // 自动转发其他未覆盖的代理方法给原始代理 override func responds(to aSelector: Selector!) -> Bool { if originalDelegate?.responds(to: aSelector) == true || super.responds(to: aSelector) { return true } return false } override func forwardingTarget(for aSelector: Selector!) -> Any? { if originalDelegate?.responds(to: aSelector) == true { return originalDelegate } return super.forwardingTarget(for: aSelector) } } class CustomWKWebView: WKWebView { private var customScrollDelegate: CustomScrollDelegate? override init(frame: CGRect, configuration: WKWebViewConfiguration) { super.init(frame: frame, configuration: configuration) setupCustomScrollDelegate() } required init?(coder: NSCoder) { super.init(coder: coder) setupCustomScrollDelegate() } private func setupCustomScrollDelegate() { customScrollDelegate = CustomScrollDelegate(originalDelegate: scrollView.delegate) scrollView.delegate = customScrollDelegate } }
这种方案能满足大多数自定义滚动行为的需求,比如监听滚动位置、拦截触摸事件等,而且完全符合Apple的开发规范。
总结一下
- 要是非得替换内置ScrollView子类,只能用Runtime Hook私有方法,但风险很高,不建议上架用;
- 优先选代理注入的方式,安全稳定,还不会踩审核的坑。
内容的提问来源于stack exchange,提问作者Tafel
相关产品推荐
相关产品推荐

