如何在 WKWebView 中禁用 iOS 11 和 iOS 12 拖放?
How to disable iOS 11 and iOS 12 Drag & Drop in WKWebView?
在 iOS 11 和 12 上的 WKWebView
中长按图像或 link 会启动拖放会话(用户可以拖动图像或 link).我怎样才能禁用它?
我确实找到了 ,但也可以禁用 WKWebView 中的拖放功能而无需任何调配。
注意:请参阅下方 iOS 12.2+ 的特别说明
WKContentView
— WKWebView
的 WKScrollView
的私有子视图 — 有一个 interactions
属性,就像任何其他 UIView
在 iOS 11+。 interactions
属性 包含 UIDragInteraction
和 UIDropInteraction
。只需在 UIDragInteraction
上将 enabled
设置为 false
即可。
我们不想访问任何私有 API 并使代码尽可能可靠。
假设您的 WKWebView
被称为 webView
:
if (@available(iOS 11.0, *)) {
// Step 1: Find the WKScrollView - it's a subclass of UIScrollView
UIView *webScrollView = nil;
for (UIView *subview in webView.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
webScrollView = subview;
break;
}
}
if (webScrollView) {
// Step 2: Find the WKContentView
UIView *contentView = nil;
// We don't want to trigger any private API usage warnings, so instead of checking
// for the subview's type, we simply look for the one that has two "interactions" (drag and drop)
for (UIView *subview in webScrollView.subviews) {
if ([subview.interactions count] > 1) {
contentView = subview;
break;
}
}
if (contentView) {
// Step 3: Find and disable the drag interaction
for (id<UIInteraction> interaction in contentView.interactions) {
if ([interaction isKindOfClass:[UIDragInteraction class]]) {
((UIDragInteraction *) interaction).enabled = NO;
break;
}
}
}
}
}
就是这样!
iOS12.2+
特别说明
以上代码在iOS 12.2 上仍然有效,但重要的是何时 调用它。在 iOS 12.1 及以下版本中,您可以在创建 WKWebView
后立即调用此代码。那不可能了。 WKContentView
的 interactions
数组在首次创建时为空。它仅在 WKWebView
添加到附加到 UIWindow
的视图层次结构后才会填充 - 仅将其添加到尚未成为可见视图层次结构一部分的父视图是不够的。在视图控制器中,viewDidAppear
很可能是调用它的安全位置。
我是怎么发现的?
- 我搜索了 WebKit 源代码并找到了这个:https://github.com/WebKit/webkit/blob/65619d485251a3ffd87b48ab29b342956f3dcdc7/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm#L4953
- 这是创建和添加
UIDragInteraction
的方法
- 原来在
WKContentView
上确实存在这个方法(setupDataInteractionDelegates
)
- 所以我在
-[WKContentView setupDataInteractionDelegates]
上设置了一个符号断点
- 命中断点
- 我使用 lldb 使用
bt
命令打印回溯
这是输出:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 50.1
* frame #0: 0x00000001115b726c WebKit`-[WKContentView(WKInteraction) setupDataInteractionDelegates]
frame #1: 0x00000001115a8852 WebKit`-[WKContentView(WKInteraction) setupInteraction] + 1026
frame #2: 0x00000001115a5155 WebKit`-[WKContentView didMoveToWindow] + 79
很明显,UIDragInteraction
的创建和添加是由视图移动到(被添加到)window 触发的。
基于 Johannes FahrenKrug 的 Post,并进行了一些更改。
private func disableDragAndDropInteraction() {
var webScrollView: UIView? = nil
var contentView: UIView? = nil
if #available(iOS 11.0, *) {
if (webView != nil) {
for subView in webView!.subviews {
if (subView is UIScrollView) {
webScrollView = subView
break
}
}
if (webScrollView != nil) {
for subView in webScrollView!.subviews {
if subView.interactions.count > 1 {
contentView = subView
break
}
}
if (contentView != nil) {
for interaction in contentView!.interactions {
if interaction is UIDragInteraction {
contentView!.removeInteraction(interaction)
}
}
}
}
} else {
// Fallback on earlier versions
}
}
}
效果很好!
感谢@basha 提供 swift 版本。
我做了同样的事情,但使用了一些 compactMaps 来减少 if 语句的深度,并使用 guards 来摆脱强制展开。
private func disableDragAndDropInteraction() {
var webScrollView: UIView? = nil
var contentView: UIView? = nil
if #available(iOS 11.0, *) {
guard let noDragWebView = webView else { return }
webScrollView = noDragWebView.subviews.compactMap { [=10=] as? UIScrollView }.first
contentView = webScrollView?.subviews.first(where: { [=10=].interactions.count > 1 })
guard let dragInteraction = (contentView?.interactions.compactMap { [=10=] as? UIDragInteraction }.first) else { return }
contentView?.removeInteraction(dragInteraction)
}
}
在 img
和 a
元素上使用 CSS 属性 webkit-touch-callout
。还可以通过更改 HTML 或使用 -[WKWebView evaluateJavaScript(...)]
.
将其 draggable
属性设置为 false
img, a {
-webkit-touch-callout: none;
}
<img draggable="false">
避免所有脆弱的子视图潜水和操作。
( to the similar 的回顾。)
我的方法是对 WKWebView 进行子类化,并以递归方式在视图层次结构中搜索包含拖放交互的子视图。一旦找到视图(很可能是 WKContentView
),就会删除交互。此方法的好处是不依赖任何子视图顺序/视图层次结构,它们可能会在 OS 版本之间发生变化。
override func didMoveToWindow() {
super.didMoveToWindow()
disableDragAndDrop()
}
func disableDragAndDrop() {
func findInteractionView(in subviews: [UIView]) -> UIView? {
for subview in subviews {
for interaction in subview.interactions {
if interaction is UIDragInteraction {
return subview
}
}
return findInteractionView(in: subview.subviews)
}
return nil
}
if let interactionView = findInteractionView(in: subviews) {
for interaction in interactionView.interactions {
if interaction is UIDragInteraction || interaction is UIDropInteraction {
interactionView.removeInteraction(interaction)
}
}
}
}
在 iOS 11 和 12 上的 WKWebView
中长按图像或 link 会启动拖放会话(用户可以拖动图像或 link).我怎样才能禁用它?
我确实找到了
注意:请参阅下方 iOS 12.2+ 的特别说明
WKContentView
— WKWebView
的 WKScrollView
的私有子视图 — 有一个 interactions
属性,就像任何其他 UIView
在 iOS 11+。 interactions
属性 包含 UIDragInteraction
和 UIDropInteraction
。只需在 UIDragInteraction
上将 enabled
设置为 false
即可。
我们不想访问任何私有 API 并使代码尽可能可靠。
假设您的 WKWebView
被称为 webView
:
if (@available(iOS 11.0, *)) {
// Step 1: Find the WKScrollView - it's a subclass of UIScrollView
UIView *webScrollView = nil;
for (UIView *subview in webView.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
webScrollView = subview;
break;
}
}
if (webScrollView) {
// Step 2: Find the WKContentView
UIView *contentView = nil;
// We don't want to trigger any private API usage warnings, so instead of checking
// for the subview's type, we simply look for the one that has two "interactions" (drag and drop)
for (UIView *subview in webScrollView.subviews) {
if ([subview.interactions count] > 1) {
contentView = subview;
break;
}
}
if (contentView) {
// Step 3: Find and disable the drag interaction
for (id<UIInteraction> interaction in contentView.interactions) {
if ([interaction isKindOfClass:[UIDragInteraction class]]) {
((UIDragInteraction *) interaction).enabled = NO;
break;
}
}
}
}
}
就是这样!
iOS12.2+
特别说明以上代码在iOS 12.2 上仍然有效,但重要的是何时 调用它。在 iOS 12.1 及以下版本中,您可以在创建 WKWebView
后立即调用此代码。那不可能了。 WKContentView
的 interactions
数组在首次创建时为空。它仅在 WKWebView
添加到附加到 UIWindow
的视图层次结构后才会填充 - 仅将其添加到尚未成为可见视图层次结构一部分的父视图是不够的。在视图控制器中,viewDidAppear
很可能是调用它的安全位置。
我是怎么发现的?
- 我搜索了 WebKit 源代码并找到了这个:https://github.com/WebKit/webkit/blob/65619d485251a3ffd87b48ab29b342956f3dcdc7/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm#L4953
- 这是创建和添加
UIDragInteraction
的方法
- 原来在
WKContentView
上确实存在这个方法( - 所以我在
-[WKContentView setupDataInteractionDelegates]
上设置了一个符号断点
- 命中断点
- 我使用 lldb 使用
bt
命令打印回溯
setupDataInteractionDelegates
)
这是输出:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 50.1
* frame #0: 0x00000001115b726c WebKit`-[WKContentView(WKInteraction) setupDataInteractionDelegates]
frame #1: 0x00000001115a8852 WebKit`-[WKContentView(WKInteraction) setupInteraction] + 1026
frame #2: 0x00000001115a5155 WebKit`-[WKContentView didMoveToWindow] + 79
很明显,UIDragInteraction
的创建和添加是由视图移动到(被添加到)window 触发的。
基于 Johannes FahrenKrug 的 Post,并进行了一些更改。
private func disableDragAndDropInteraction() {
var webScrollView: UIView? = nil
var contentView: UIView? = nil
if #available(iOS 11.0, *) {
if (webView != nil) {
for subView in webView!.subviews {
if (subView is UIScrollView) {
webScrollView = subView
break
}
}
if (webScrollView != nil) {
for subView in webScrollView!.subviews {
if subView.interactions.count > 1 {
contentView = subView
break
}
}
if (contentView != nil) {
for interaction in contentView!.interactions {
if interaction is UIDragInteraction {
contentView!.removeInteraction(interaction)
}
}
}
}
} else {
// Fallback on earlier versions
}
}
}
效果很好! 感谢@basha 提供 swift 版本。
我做了同样的事情,但使用了一些 compactMaps 来减少 if 语句的深度,并使用 guards 来摆脱强制展开。
private func disableDragAndDropInteraction() {
var webScrollView: UIView? = nil
var contentView: UIView? = nil
if #available(iOS 11.0, *) {
guard let noDragWebView = webView else { return }
webScrollView = noDragWebView.subviews.compactMap { [=10=] as? UIScrollView }.first
contentView = webScrollView?.subviews.first(where: { [=10=].interactions.count > 1 })
guard let dragInteraction = (contentView?.interactions.compactMap { [=10=] as? UIDragInteraction }.first) else { return }
contentView?.removeInteraction(dragInteraction)
}
}
在 img
和 a
元素上使用 CSS 属性 webkit-touch-callout
。还可以通过更改 HTML 或使用 -[WKWebView evaluateJavaScript(...)]
.
draggable
属性设置为 false
img, a {
-webkit-touch-callout: none;
}
<img draggable="false">
避免所有脆弱的子视图潜水和操作。
(
我的方法是对 WKWebView 进行子类化,并以递归方式在视图层次结构中搜索包含拖放交互的子视图。一旦找到视图(很可能是 WKContentView
),就会删除交互。此方法的好处是不依赖任何子视图顺序/视图层次结构,它们可能会在 OS 版本之间发生变化。
override func didMoveToWindow() {
super.didMoveToWindow()
disableDragAndDrop()
}
func disableDragAndDrop() {
func findInteractionView(in subviews: [UIView]) -> UIView? {
for subview in subviews {
for interaction in subview.interactions {
if interaction is UIDragInteraction {
return subview
}
}
return findInteractionView(in: subview.subviews)
}
return nil
}
if let interactionView = findInteractionView(in: subviews) {
for interaction in interactionView.interactions {
if interaction is UIDragInteraction || interaction is UIDropInteraction {
interactionView.removeInteraction(interaction)
}
}
}
}