Flutter 官方正式解决 WebView 在 iOS 26 上有点击问题

Flutter 官方正式解决 WebView 在 iOS 26 上有点击问题

上个月和大家聊到了 《为什么你的 Flutter WebView 在 iOS 26 上有点击问题?》 ,源头是因为 WKWebView(WebKit)内部的手势识别器与 Flutter 在 Engine 里用于“阻止/延迟”手势的 recognizer 之间的冲突,因为 Flutter 和 UIKit 都各自有手势识别系统(GestureRecognizer),为了防止互相抢事件,Flutter engine 在 iOS 上加入了一个“delaying gesture recognizer”(延迟识别器),这也最终导致了 iOS 26 上的 bug :

在 Flutter 弹窗和 WKWebView 一起出来的时候,要么点不动,要么触摸会穿透到下面的 WebView 。

而在提供了之前部分场景有效的临时解决方案之后,Flutter 官方也提出了几个对应的可行性重构方案,具体可见 https://docs.google.com/document/d/1ag4drAdJsR7y-rQZkqJWc6tOQ4qCbflQSGyoxsSC6MM/ ,而现在方案三最终确定并 LGTM :

回顾整个问题里程,主要有两点:

  • 现有的 “gesture recognizer approach” (依赖自定义 UIGestureRecognizer + shouldRequireFailureOfGestureRecognizer)存在局限:无法阻止 UIView / JS 的底层 touch 回调(例如 WebView 的 touchstart),并且会和 WebView 的内部识别器冲突(这个导致了 iOS 的平台 bug),从而让一直以来的 hack 实现(如 remove/re-add recognizer)不生效:
因为 JS的 touchstarttouchesBegan 当帧同步触发,Flutter 没法在 touchesBegan 前屏蔽掉事件。
  • 以前为了解决 Google Maps 的 “dangling touchesBegan” 问题,引入了 WaitUntilTouchesEnded 策略,这是个权宜之计但并不理想,本质也是一种延迟机制:
因为当时 Flutter 没有能力在 touchesBegan 之前阻止触摸的到达,只能用 gesture recognizer 阻断,而这就导致了 Google Maps 在 touchesBegan 之后,后续 touchesEnded 会变成 recognizer fails 从而不会收到 touchesEnded,而这就是 WaitUntilTouchesEnded 诞生的背景,WaitUntilTouchesEnded 的目的就是避免 Google Maps 在中途被强制 fail,导致内部手势状态机 fails。

其实这一切的原因都是已经“异步协同”,所以现在修复开始改为“同步”,也就是 Flutter Engine + iOS embedder 新增了 “同步 hitTest 回调” 能力 :

  • iOS embedder 增加了可拦截 hitTest 的 UIView
  • Engine 与 Dart Framework 通过 FFI 实现“同步回调”

具体来说就是,首先是 Dart Framework 层,这里新增手势拦截策略 API (UiKitView),在 UiKitView 组件中新增了 gestureRecognizersBlockingPolicy 参数,让开发者可以为每个 PlatformView 单独配置手势拦截行为:

策略名称拦截时机使用技术解决核心问题
touchBlockingOnly最快 (HitTest 阶段)iOS hitTest 重写修复 iOS 18+/26 WebView/AdMob 无法点击
eager快 (手势竞争胜出时)阻塞手势识别器(旧默认值,现已不推荐)
waitUntilTouchesEnded慢 (手指抬起后)阻塞手势识别器过去修复 Google Maps 状态卡死问题
fallbackToPluginDefault(取决于插件设定)(取决于插件设定)保持旧插件兼容性
新机制让开发者可以在 Dart 代码中直接指定 PlatformView 的手势拦截策略,而不是依赖全局配置或原生代码,根据不同场景配置不同的拦截处理机制,而 touchBlockingOnly 就是全新的支持。

例如针对 AdMob 或 WebView 在 iOS 18+/26 上的点击穿透问题,现在开发者可以强制使用 touchBlockingOnly 策略,从而绕过有问题的 gesture recognizer 机制。

另外也提供了 fallbackToPluginDefault,确保不修改代码的情况下维持原有插件的行为。

接着就是在 Dart Framework 层实现了 Hit Test 逻辑,用于响应 Engine 发起的命中测试请求,判断点击位置是否落在 PlatformView 上:

当用户点击屏幕时,Flutter 通过 Render Tree 判断该位置最上层是否是 PlatformView,如果是被 Flutter 组件(如下拉菜单)遮挡,firstHit 就不会是 NativeHitTestTarget,从而拦截触摸。

然后就是 Engine / Bridge 层的通信,这部分主要负责将 iOS 原生的同步调用桥接到 Dart 环境,这里提供了一个同步的 Dart 入口点 _platformViewShouldAcceptTouch,从而让 iOS 原生的 hitTest 方法可以阻塞等待 Dart 的判断结果:

之后就是 iOS Engine 层重写了 hitTest 方法,这也是本次修复的核心,通过重写 PlatformView 的 hitTest 方法,在通过响应链传递触摸事件之前,先询问 Flutter Framework 是否应该拦截该事件:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { if (_blockingPolicy == FlutterPlatformViewGestureRecognizersBlockingPolicyTouchBlockingOnly) { // // ... (获取 flutterViewController) CGPoint pointInFlutterView = [self convertPoint:point toView:self.flutterViewController.view]; // 询问 Framework 是否应该接收此触摸 if (![self.flutterViewController platformViewShouldAcceptTouchAtTouchBeganLocation:pointInFlutterView]) { // // 如果 Framework 说 "不" (例如点击了 Flutter 遮罩),返回 self (拦截触摸) return self; } } // 如果 Framework 说 "是",调用 super,让事件传递给 WKWebView return [super hitTest:point withEvent:event]; } 

而对应的就是,如果策略是 TouchBlockingOnly,则不再添加容易导致冲突的 delayingRecognizer :

也就是,现在会先通过 hitTest 预先判断,避免了使用 UIGestureRecognizerDelegate 带来的复杂性和 iOS 18+ 上的 Bug,而当 platformViewShouldAcceptTouch 返回 NO 时,FlutterTouchInterceptingView 自身会吞掉事件,底层的 WebView 就不会收到错误的点击,从而修复了点击穿透或链接无法点击的问题。

最后

可以看到,本次调整数据较大的底层变动,所以牵动的模块也比较多,这也是为什么这个 PR 一直拖到现在才合并的原因,因为需要考虑和测试的因素很多:

而对于开发者来说,如果要引用修复,最好是通过增加对应的 gestureBlockingPolicy 参数来支持配置,只针对有问题的场景使用 touchBlockingOnly ,因为这怎么说也是一个底层大变更,会不会有新的问题还不好说。

参考链接

  • https://github.com/flutter/flutter/pull/179659
  • https://github.com/flutter/flutter/issues/175099

Read more

架构大揭秘:单 Agent vs. 多 Agent,你的 AI 团队该怎么组建?

架构大揭秘:单 Agent vs. 多 Agent,你的 AI 团队该怎么组建?

架构大揭秘:单 Agent vs. 多 Agent,你的 AI 团队该怎么组建? 文章目录 * 架构大揭秘:单 Agent vs. 多 Agent,你的 AI 团队该怎么组建? * 前言:AI 世界的“单打独斗”与“团队协作” * 一、专业解读:Agent 的“独行侠”与“群英会” * 1.1 单 Agent:披荆斩棘的“全能战士” * 1.2 多 Agent:分工协作的“梦之队” * 1.3 核心对比:单 Agent vs.

Meta-Llama-3-8B-Instruct性能优化:推理速度提升50%实战

Meta-Llama-3-8B-Instruct性能优化:推理速度提升50%实战 1. 背景与挑战:为何需要对Llama-3-8B进行推理加速 随着大语言模型在对话系统、代码生成和智能助手等场景的广泛应用,用户对响应速度的要求日益提高。Meta-Llama-3-8B-Instruct 作为一款具备强大英文指令理解能力、支持8k上下文且可在消费级显卡上运行的开源模型,已成为许多开发者构建本地化AI应用的首选。 然而,默认使用Hugging Face Transformers加载该模型时,存在推理延迟高、吞吐低的问题,尤其在多轮对话或批量请求场景下表现明显。例如,在RTX 3060(12GB)上使用fp16加载,首词生成延迟可达800ms以上,整体输出速度仅为12-15 token/s,难以满足实时交互需求。 本文将围绕 vLLM + Open WebUI 架构,详细介绍如何通过PagedAttention、连续批处理(Continuous Batching)和量化部署等关键技术,实现Meta-Llama-3-8B-Instruct推理性能提升50%以上,并结合DeepSeek-

Qwen3-32B智能写作实测:云端1小时生成5万字,成本2元

Qwen3-32B智能写作实测:云端1小时生成5万字,成本2元 你是不是也是一位网文作者?每天面对更新压力,卡文、断更、灵感枯竭成了常态。最近几年AI写作火了,你也听说“大模型能自动写小说”,于是兴致勃勃地下载了一个本地模型,结果发现——根本跑不动!提示信息写着“显存不足”,查了一下才知道,原来像Qwen3-32B这样的大模型,动辄需要24GB甚至更高的显存,普通电脑的集成显卡或入门级独显根本扛不住。 你转战免费平台,比如Colab,却发现每次运行到一半就断连,保存的内容全丢了,还得重新加载模型,效率极低。更别提那些复杂的配置命令,光是安装依赖就能劝退一大片人。有没有一种既稳定、又便宜、还能快速上手的方案? 答案是:有。而且我已经亲自测试过——用ZEEKLOG星图提供的Qwen3-32B镜像,在云GPU环境下,1小时内生成超过5万字的小说内容,总成本不到2元。整个过程一键部署,无需任何复杂操作,连Python都不会也能搞定。 这篇文章就是为你写的。我会带你从零开始,一步步在云端部署Qwen3-32B模型,实现高效稳定的AI辅助写作。无论你是想批量生成章节草稿、拓展剧情支线,还是

免费使用AI绘画模型Nano Banana Pro,太有意思了!

大家好,我是程序员小灰。 关注AI的朋友们应该都知道,最近谷歌的AI模型Gemini3.0 Pro爆火了。Gemini3是一个AI大家族,这个家族的成员不只有善于文字对话的大语言模型,还包括着善于绘画的图像生成模型,Nano Banana Pro。 这两天小灰亲自测试了Nano Banana Pro模型,虽然说不上完美,但真的十分有趣!(后文简称Nano Banana) 如何免费使用Nano Banana?  想要免费使用Nano Banana,小灰给大家推荐三个地方: 1.Gemini官网 入口如下: https://gemini.google.com/ 在Gemini官网的对话框下方,点击“制作图片”选项,就可以和Nano Banana模型进行对话: 2.Google AI Studio 入口如下: https://aistudio.google.com/ 在Google AI Studio的首页右下角,点击“