如何在WKWebView中禁用iOS 11和iOS 12的拖放功能?

4

在iOS 11和12上,长按WKWebView中的图像或链接会启动拖放会话(用户可以拖动图像或链接)。我该如何禁用它?

5个回答

7

我确实找到了一种涉及方法交换的解决方案,但是在不进行任何交换的情况下也可以禁用WKWebView中的拖放。

注意:请参阅以下iOS 12.2+的特殊说明

WKContentViewWKWebViewWKScrollView的一个私有子视图,与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.2上仍然有效,但重要的是要知道何时调用它。在iOS 12.1及以下版本中,您可以在创建WKWebView后立即调用此代码。现在不可能了。当首次创建WKContentView时,其interactions数组为空。只有在将WKWebView添加到附加到UIWindow的视图层次结构中时,它才会被填充 - 只是将其添加到尚未成为可见视图层次结构的父视图中是不够的。在视图控制器viewDidAppear中最有可能是从中调用它的安全位置。

我是如何发现这一点的?

这是输出结果:

* 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是由视图移动到(被添加到)窗口触发的。

3

这个很棒!感谢 @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)
    }
}

1
有没有特别的原因不使用 first(where: ...) 而是使用 compactMap(...).first?我想知道,因为你正在使用它来查找 contentView - Mickaël
没有特别的原因,使用 first(where:) 也可以解决问题。 - Sebbo
UITextView 有类似的解决方案吗? - Hassan Taleb


2

基于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
        }
    }
}

1

我的做法是通过子类化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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接