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:
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.
Fortunately, UIKit has a dedicated property that deactivates the swipe to dismiss behavior: isModalInPresentation . This is false by default, but if you set it to true then UIKit will stop the interactive dismiss gesture and also ignore any events that occur outside of the detail view controller's bounds.
The UIViewController class defines the shared behavior that's common to all view controllers. You rarely create instances of the UIViewController class directly. Instead, you subclass UIViewController and add the methods and properties needed to manage the view controller's view hierarchy.
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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With