4

I have a WKWebView in my app. Everything is running smooth except the website I show has the social media logon buttons. The problem is, they display (or try to display) a popup where you allow it to have access to your social media account. I have researched this and tried a few things, but none seem to fit. Here is what I tried:

in viewDidLoad, I tried to enable Javascript on the init:

WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
WKPreferences *thePreferences = [[WKPreferences alloc] init];
thePreferences.javaScriptCanOpenWindowsAutomatically = YES;
thePreferences.javaScriptEnabled = YES;
theConfiguration.preferences = thePreferences;
self.wkWeb = [[WKWebView alloc] initWithFrame:screenRect configuration:theConfiguration];

However, this didn't help me. Then I tried to play with the delegates and go that route. I tried playing around with the createWebViewWithConfiguration method, but that seems like overkill because I had to filter out if they are at the login URL then configure a popup and display it. And then this still wasn't working. I also come in here for the if not main frame logic, cancel the request and reload it in the main view, and that is an easy one-line fix where as this was ballooning into 20+ lines.

This seems like a common problem, but I can't seem to find a lot of information out there. Any ideas?

EDIT - Addition

After playing around with Armands answer, I get closer. This is my createWebViewWithConfig method now, which just displays a white screen overlay. It's like I need something to tell the new popup to load the content. Also - I assume I can place this modal window in my current .m file and it doesn't need a completely new file?

NSURL *url = navigationAction.request.URL;
if(!navigationAction.targetFrame.isMainFrame && [url.absoluteString rangeOfString:@"initiate_"].location != NSNotFound) {
    //open new modal webview
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.processPool = [appDelegate getSharedPool];

    self.popupLogin = [[WKWebView alloc] initWithFrame:self.wkWeb.bounds configuration:config];
    self.popupLogin.frame = CGRectMake(self.wkWeb.frame.origin.x,
                                       self.wkWeb.frame.origin.y,
                                       self.wkWeb.frame.size.width,
                                       self.wkWeb.frame.size.height);
    self.popupLogin.navigationDelegate = self;
    self.popupLogin.UIDelegate = self;
    [webView addSubview:self.popupLogin];
    [self.popupLogin loadRequest:navigationAction.request];
    return nil;
}
Justin Pfenning
  • 433
  • 1
  • 5
  • 18

5 Answers5

11

Swift 4 and Swift 5

Create two instance of WKWebView

var webView : WKWebView!
var newWebviewPopupWindow: WKWebView?

Now implement the methods

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        newWebviewPopupWindow = WKWebView(frame: view.bounds, configuration: configuration)
        newWebviewPopupWindow!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        newWebviewPopupWindow!.navigationDelegate = self
        newWebviewPopupWindow!.uiDelegate = self
        view.addSubview(newWebviewPopupWindow!)
        return newWebviewPopupWindow!
    }

    func webViewDidClose(_ webView: WKWebView) {
        webView.removeFromSuperview()
        newWebviewPopupWindow = nil
    }

Also make sure this string in the Info.plist if your code is still not working.

 <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
Rashid Latif
  • 2,809
  • 22
  • 26
5

Those login buttons tries to open a new tab, which is not supported by WKWebView. As far as I know, there's only one way to do this.

First add WKUIDelegate method:

-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
    NSURL *url = navigationAction.request.URL;
    if (navigationAction.targetFrame == nil && [url.absoluteString rangeOfString:@"facebook.com/dialog"].location != NSNotFound) {
        //Open new modal WKWebView
    }
    return nil;
}

In the modal WKWebView add this:

-(void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
    NSURL *url = navigationResponse.response.URL;
    if ([url.absoluteString rangeOfString:@"close_popup.php"].location != NSNotFound) {
        //Close viewController
    }
    decisionHandler(WKNavigationResponsePolicyAllow);
}

close_popup.php is for Facebook. If you need to support multiple social networks, find something unique in their URLs.

In order for this to work, you will have to share Cookies between WKWebViews. To do this you have to init them with shared WKProcessPool. I prefer to store it in AppDelegate.

WKWebView can be created like this:

- (void)createWebView
{
    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.processPool = delegate.webViewProcessPool;

    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
    self.webView.UIDelegate = self;
    self.webView.navigationDelegate = self;
    [self.view addSubview:self.webView];
}

In AppDelegate just lazy load it:

-(id)webViewProcessPool
{
    if (!_webViewProcessPool) {
        _webViewProcessPool = [[WKProcessPool alloc] init];
    }
    return _webViewProcessPool;
}
Armands L.
  • 1,885
  • 2
  • 14
  • 18
  • Thank you for the assistance. It is further than it has ever been. I don't get the 503 forbidden error anymore. The problem I am hitting now is that this code just shows a white window over the current webView. I can verify it is over because I didn't set the autoresizing mask and when I rotate my phone I can see the underneath. I think my problem is on the createWebViewWithConfiguration method. I can't post the whole method as it is too long. I will update my original question to include it. – Justin Pfenning Aug 22 '16 at 17:35
  • After much digging and researching and coding and pulling out my hair I decided to try it on Safari outside of WKWebView. What do you know - it doesn't work there. I was trying to troubleshoot a serverside error in my app. After that gets correct, it works now. Thanks so much for your help. Marking your answer as correct. – Justin Pfenning Aug 23 '16 at 16:47
  • Great. I'm glad this helped. – Armands L. Aug 24 '16 at 08:06
  • According to the documentation, the `targetFrame` is `nil` when a new window should be opened. So the code here works because `navigationAction.targetFrame` is `nil` which makes `.isMainFrame` equivalent to `NO`. But actually you'd want to test `navigationAction.targetFrame == nil`. – DarkDust Sep 01 '17 at 11:44
5

Armands' answer is on the right track but can be improved significantly.

  • It's important to return the newly created webview (and not nil) if you want the webviews to be able to work with each other (e.g., via window.opener)
  • Sharing the process pool happens automatically if you use the configuration passed in to the create call
  • You don't need the extra checks in the createWebViewWith call. (You should still whitelist URLs if you want to prevent arbitrary popup windows or create specific WebViewController types.)

In your main WebViewController (where webView.uiDelegate = self):

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    let webView = WKWebView.init(frame: .zero, configuration: configuration)

    //OPTIONAL: check navigationAction.request.url to see what site is being opened as a popup

    //TODO HERE: Launch new instance of your WebViewController (within a nav controller)
    //and pass it this newly created webView to be added as main subview

    return webView
}

You do still want to have the close listener in the new instance of your popup WebViewController (where webView.navigationDelegate = self):

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if let url = navigationResponse.response.url, url.absoluteString.hasPrefix("https://example.com/something-unique-to-this-popup-when-it-closes") {
        dismiss(animated: true, completion: nil)
    }
    decisionHandler(.allow)
}
AlexD
  • 813
  • 9
  • 15
1

Swift4/5 snippet - using MyWebView: extended WkWebView:

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
             for navigationAction: WKNavigationAction,
             windowFeatures: WKWindowFeatures) -> WKWebView? {
    if navigationAction.targetFrame == nil {
        let newView = MyWebView.init(frame: webView.frame, configuration: configuration)

        newView.customUserAgent = webView.customUserAgent
        newView.navigationDelegate = self
        newView.uiDelegate = self

        self.view.addSubview(newView)
        newView.fit(newView.superview!)

        newView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
        newView.configuration.preferences.javaScriptEnabled = true
        newView.configuration.preferences.plugInsEnabled = true
        newView.load(navigationAction.request)

        return newView
    }
slashlos
  • 913
  • 9
  • 17
0

Add the following handlers and the popup window loads fine.

# pragma mark WKUIDelegate
- (nullable WKWebView *)webView:(WKWebView *)webView
createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures
(WKWindowFeatures *)windowFeatures
{
if (navigationAction.targetFrame == nil) {
    _popupWebViewObject = [[WKWebView alloc] initWithFrame:webView.frame configuration:configuration];
    _popupWebViewObject.customUserAgent = webView.customUserAgent;
    _popupWebViewObject.UIDelegate = self;
    _popupWebViewObject.navigationDelegate = self;
    [self.window.contentView addSubview:_popupWebViewObject];
    _popupWebViewObject.configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES;
    _popupWebViewObject.configuration.preferences.javaScriptEnabled = YES;
    _popupWebViewObject.configuration.preferences.plugInsEnabled = YES;
    [_popupWebViewObject loadRequest:[navigationAction.request copy]];
    return _popupWebViewObject;
}
return nil;
}

- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
    NSLog(@"%s", __FUNCTION__);
    NSLog(@"%@ TOKEN : ",webView.URL.absoluteString.lowercaseString);
    [self dismissPopWindow:webView];
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    [self dismissPopWindow:webView];
    decisionHandler(WKNavigationActionPolicyAllow);
}
KamyFC
  • 858
  • 9
  • 17