7

I have a view controller (WKWebViewController) that is embedded in a NavigationController. This ViewController presents a WKWebView. After navigating to any web page; and upon long-pressing any detected content, such as a phone number or a link, an action sheet is displayed with options like copy, share, etc. The issue is when this action sheet is dismissed, the WKWebViewController gets dismissed along with it and the root ViewController is displayed! Regardless of the what the selection was be it Copy, Cancel, or even if tapped anywhere on the screen.

I've tried overriding the "present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil)" and the "dismiss(animated flag: Bool, completion: (() -> Void)?)" in an attempt to understand what is happening but then realized that the action sheet is not being presented neither dismissed by its parent view controller (WKWebViewController), the matter of fact I did the same on the root view controller and found that it is not presented on it neither.

I've done a lot of searching trying to understand what is causing this behavior, I even built a new project with a simple WKWebView only and always ended up with the same problem.

Here is the code:

import UIKit; import WebKit
class WKWebViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {

var destinationUrlString: String?
var myWebView: WKWebView!

override func viewDidLoad() {
    super.viewDidLoad()

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.dataDetectorTypes = []
    let origin = CGPoint(x: 0, y: 0)
    let size  = CGSize(width: view.frame.size.width, height: view.frame.size.height)
    myWebView = WKWebView(frame: .init(origin: origin, size: size), configuration: webConfiguration)
    myWebView.uiDelegate = self
    myWebView.navigationDelegate = self
    myWebView.allowsLinkPreview = false
    view = myWebView

    destinationUrlString = "https://www.stackoverflow.com"
    guard let url = URL(string: destinationUrlString!) else {return}
    print(url)
    let request = URLRequest(url: url)
    myWebView.load(request)
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    //show progress indicator
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    //dismiss progress indicator
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
    //show error
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    //show error
}
}

I've also attached a GIF showing the issue:

enter image description here

I am using Xcode 9.3 (9E145) and Swift 4.1.

Am I missing something? How can this be fixed? Any help would be really appreciated.

Shivam Tripathi
  • 1,405
  • 3
  • 19
  • 37
ferasfa
  • 71
  • 1
  • 5
  • Did you try this https://stackoverflow.com/questions/37380333/modal-view-closes-when-selecting-an-image-in-wkwebview-ios – Vinaykrishnan Apr 16 '18 at 12:42
  • Yes, but it doesn't work. As I explained, it is not the parent view controller that is presenting the action sheet "callout" menu, thus overriding the _present_ and _dismiss_ doesn't work. – ferasfa Apr 16 '18 at 12:53
  • you should show us your code for the action sheet. Did you set up a `.cancel` style action? – thorb65 Apr 17 '18 at 08:52
  • 1
    I am not doing the action sheet myself, so there is no code to show. I am using the **default** one that pops when any of the _dataDetectorTypes_ is detected. ex: a phone number is detected, if you long press it the sheet will popup with options like: Share, Copy, Call, etc. – ferasfa Apr 17 '18 at 09:09
  • @ferasfa Did you solve this ? – el3ankaboot May 04 '20 at 13:02

3 Answers3

10

I've experienced the same problem.

To deal with this situation, in the root view controller of the UIViewController's hierarchy (in your case this will be the "Root View Controller" — note, in many cases it might be a UINavgiationController which you'd then have to subclass) override dismissViewControllerAnimated:completion: to deal with the WKWebView calling dismiss multiple times.

Objective-C

@property (weak) UIViewController *lastPresentedController;

- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
    // WKWebView actions sheets workaround
    if (self.presentedViewController && self.lastPresentedController != self.presentedViewController ) {
        self.lastPresentedController = self.presentedViewController;
        [self.presentedViewController dismissViewControllerAnimated:YES completion:^{
            if( completion ) {
                completion();
            }
            self.lastPresentedController = nil;
        }];
    } else if( !self.lastPresentedController) {
        [super dismissViewControllerAnimated:flag completion:completion];
    }
}

Swift

private weak var lastPresentedController : UIViewController?

override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
    // WKWebView actions sheets workaround
    if presentedViewController != nil && lastPresentedController != presentedViewController  {
        lastPresentedController = presentedViewController;
        presentedViewController?.dismiss(animated: flag, completion: {
            completion?();
            self.lastPresentedController = nil;
        });

    } else if lastPresentedController == nil {
        super.dismiss(animated: flag, completion: completion);
    }
}
shim
  • 9,289
  • 12
  • 69
  • 108
shc
  • 369
  • 2
  • 8
  • 3
    This worked for me. However, I added this code snippet to the NavigationController code instead of a UIViewController. – Jeremy Bell Jun 20 '18 at 15:36
  • I had to put the code in the view controller that contains the WKWebView because it was presented. (iOS 11.4.1) – Daniel T. Nov 06 '18 at 16:47
1

This has to be a bug. If you override dismiss in your viewController housing the web view, it will get called when the user selects any action from the action sheet. If you put a breakpoint in your dismiss override, you can see that the presentedViewController is of type WKActionSheet which is an internal class. They're likely calling dismiss to close the action sheet, which is then trickling up to and dismissing our view controller housing the web view. You can return from within that function to prevent the closing, but then the action they chose also doesn't happen.

If you don't want to file a radar, let me know and I will.

LordBron
  • 41
  • 2
  • I do think it is a bug. I've already reported it to Apple but to be honest I am not expecting to hear back from them any time soon. I also found a radar for this issue here: http://www.openradar.me/34547298. Feel free to file a new one. – ferasfa Apr 26 '18 at 10:29
  • Reported it a few weeks ago and it was marked as a duplicate of 34960698 (which doesn't have an open radar) – shim Jun 28 '18 at 15:44
  • 2
    @shim the duplicated ticket was listed in :https://webkit.googlesource.com/WebKit/+/master/Source/WebKit/ChangeLog It says fixed? o.O whut. i also countered the same problem. – JackyW Jul 27 '18 at 04:52
  • Didn't even know WebKit was open source. Yea it looks like they're claiming it was resolved in March, and there have been new iOS releases since then and they still had the problem. – shim Jul 27 '18 at 04:54
0
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion{
    if (@available(iOS 11, *)) {
        [super dismissViewControllerAnimated:flag completion:completion];
    }
    else{
        if ([self.topViewController isKindOfClass:[RTContainerController class]]){
            RTContainerController *topVC = (RTContainerController *)self.topViewController;
            if (![topVC.contentViewController isKindOfClass:NSClassFromString(@"MyWebViewController")]) {
                //让非 web 页面可以正常消失
                [self.topViewController dismissViewControllerAnimated:flag completion:completion];
            }else{
                //防止 web 页面消失
                if (self.presentedViewController) {
                    [self.presentedViewController dismissViewControllerAnimated:flag completion:completion];
                }
            }
        }else{
            if (self.presentedViewController) {
                [self.presentedViewController dismissViewControllerAnimated:flag completion:completion];
            }
        }
    }
}
jiangjiefs
  • 149
  • 11