在iOS 11和12上,长按WKWebView
中的图像或链接会启动拖放会话(用户可以拖动图像或链接)。我该如何禁用它?
在iOS 11和12上,长按WKWebView
中的图像或链接会启动拖放会话(用户可以拖动图像或链接)。我该如何禁用它?
我确实找到了一种涉及方法交换的解决方案,但是在不进行任何交换的情况下也可以禁用WKWebView中的拖放。
注意:请参阅以下iOS 12.2+的特殊说明
WKContentView
是WKWebView
的WKScrollView
的一个私有子视图,与iOS 11+中的任何其他UIView
一样,都具有interactions
属性。该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;
}
}
}
}
}
就是这样!
以上代码在iOS 12.2上仍然有效,但重要的是要知道何时调用它。在iOS 12.1及以下版本中,您可以在创建WKWebView
后立即调用此代码。现在不可能了。当首次创建WKContentView
时,其interactions
数组为空。只有在将WKWebView
添加到附加到UIWindow
的视图层次结构中时,它才会被填充 - 只是将其添加到尚未成为可见视图层次结构的父视图中是不够的。在视图控制器viewDidAppear
中最有可能是从中调用它的安全位置。
UIDragInteraction
的方法setupDataInteractionDelegates
)实际上存在于WKContentView
上-[WKContentView setupDataInteractionDelegates]
上设置了一个符号断点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
是由视图移动到(被添加到)窗口触发的。这个很棒!感谢 @basha 提供的快速版本。
我也做了同样的事情,但使用了一些 compactMaps 来减少 if 语句和 guard 的层数,以消除强制解包。
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 { $0 as? UIScrollView }.first
contentView = webScrollView?.subviews.first(where: { $0.interactions.count > 1 })
guard let dragInteraction = (contentView?.interactions.compactMap { $0 as? UIDragInteraction }.first) else { return }
contentView?.removeInteraction(dragInteraction)
}
}
基于Johannes FahrenKrug的帖子,做了一些改动。
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
}
}
}
我的做法是通过子类化WKWebView并在视图层次结构中递归搜索包含拖放交互的子视图。找到一个视图之后(很可能是WKContentView
),将交互删除。这种方法的好处是不依赖于任何子视图顺序/视图层次结构,因为这些在操作系统发布之间可能会发生变化。
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)
}
}
}
}
interaction.enabled = false
而不是移除它,但我想这只是实现相同目标的两种不同方法。 - Johannes Fahrenkrug
first(where: ...)
而是使用compactMap(...).first
?我想知道,因为你正在使用它来查找contentView
。 - Mickaël