How to disable iOS 11 and iOS 12 Drag & Drop in WKWebView?

青春壹個敷衍的年華 提交于 2020-08-25 14:52:47

问题


Long-pressing images or links in a WKWebView on iOS 11 and 12 initiates a Drag & Drop session (the user can drag the image or the link). How can I disable that?


回答1:


I did find a solution that involves method swizzling but it's also possible to disable drag and drop in a WKWebView without any swizzling.

Note: See special notes for iOS 12.2+ below

WKContentView — a private subview of WKWebView's WKScrollView — has an interactions property, just like any other UIView in iOS 11+. That interactions property contains both a UIDragInteraction and a UIDropInteraction. Simply setting enabled to false on the UIDragInteraction does the trick.

We don't want to access any private APIs and make the code as solid as possible.

Assuming your WKWebView is called 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;
                }
            }
        }
    }
}

That's it!

Special note for iOS 12.2+

The above code still works on iOS 12.2, but it is important when to call it. On iOS 12.1 and below you could call this code right after creating the WKWebView. That's not possible anymore. The WKContentView's interactions array is empty when it's first created. It is only populated after the WKWebView is added to a view hierarchy that is attached to a UIWindow - simply adding it to a superview that is not yet part of the visible view hierarchy is not enough. In a view controller viewDidAppear would most likely be a safe place to call it from.

How did I find this out?

  • I searched through the WebKit source and found this: https://github.com/WebKit/webkit/blob/65619d485251a3ffd87b48ab29b342956f3dcdc7/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm#L4953
  • That's the method that creates and adds the UIDragInteraction
  • It turns out that this method (setupDataInteractionDelegates) actually exists on WKContentView
  • So I set a symbolic breakpoint on -[WKContentView setupDataInteractionDelegates]
  • The breakpoint was hit
  • I used lldb to print the backtrace using the bt command

This was the output:

* 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 

So clearly the creation and addition of the UIDragInteraction is triggered by the view moving to (being added to) a window.




回答2:


This works great! Thanks @basha for the swift version.

I did the same but with some compactMaps to reduce the depth of the if-statements and guards to get rid of the force unwraps.

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)
    }
}



回答3:


Based on Johannes FahrenKrug's Post, with some changes.

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


来源:https://stackoverflow.com/questions/49911060/how-to-disable-ios-11-and-ios-12-drag-drop-in-wkwebview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!