7
votes

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.

3
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
you should show us your code for the action sheet. Did you set up a .cancel style action?thorb65
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
@ferasfa Did you solve this ?el3ankaboot

3 Answers

9
votes

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);
    }
}
1
votes

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.

0
votes
- (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];
            }
        }
    }
}