How to prevent WKWebView to repeatedly ask for permission to access location?
Asked Answered
V

6

27

I have a WKWebView in my app and when I start browsing www.google.com or any other website that requires location service, a pop up window appears, asking for the permission to access the location of the device even if I have already accepted to share my location.

The only thing I did to manage this location stuff is that I added the NSLocationWhenInUseUsageDescription attribute in my info.plist.

I couldn't find any answers on the web so any idea would be really appreciated.

Varhol answered 23/9, 2016 at 16:19 Comment(0)
B
19

Turns out it's quite hard, but possible to do. You have to inject JavaScript code which intercepts requests to navigator.geolocation and transfer them to your app, then get the location with CLLocationManager, then inject location back to the JavaScript.

Here is the brief scheme:

  1. Add WKUserScript to your WKWebView configuration which overrides methods of navigator.geolocation. Injected JavaScript should look like this:

    navigator.geolocation.getCurrentPosition = function(success, error, options) { ... };
    navigator.geolocation.watchPosition = function(success, error, options) { ... };
    navigator.geolocation.clearWatch = function(id) { ... };
    
  2. With WKUserContentController.add(_:name:) add script message handler to your WKWebView. Injected JavaScript should call your handler, like this:

    window.webkit.messageHandlers.locationHandler.postMessage('getCurrentPosition');
    
  3. When a web page will request a location, this method will fire userContentController(_:didReceive:) so your app would know web page is requesting location. Find your location with the help of CLLocationManager as usual.

  4. Now it's time to inject the location back to the requesting JavaScript with webView.evaluateJavaScript("didUpdateLocation({coords: {latitude:55.0, longitude:0.0}, timestamp: 1494481126215.0})"). Of course your injected JavaScript should have didUpdateLocation function ready to launch saved success handler.

Quite a long algorithm, but it works!

Backfire answered 11/5, 2017 at 5:47 Comment(12)
this saved my day :)Rickets
Hi... can u provide GitHub example?Jamey
@PanMluvčí Sorry, right now I don't have time for publishing full-fledged solution. I can answer specific questions though.Backfire
Hi... any chance to show more detailed answer? I am lost at 1st markJamey
Hi @AlexanderVasenin, can you provide a detailed answer or a GitHub example is appreciatedVenomous
I dont receieve userContentController(_:didReceive:) message name and body.Iaea
@AlexanderVasenin can u please provide a sample now?Schneider
@AlexanderVasenin I just created a sample gist.github.com/hayahyts/2c369563b2e9f244356eb6228ffba261Schneider
@PanMluvčí see my answer - there is full code example and can be easily integrated (just 4 lines in ViewController)Moreta
@GisonGeorge see my answer - there is full code example and can be easily integrated (just 4 lines in ViewController)Moreta
Is this approach still the only way to avoid the double location popup with iOS 13.3? I'm trying to submit an app and it's being rejected because the second popup does not provide the reason from the NSLocationWhenInUseUsageDescription attribute in the plist. How Apple doesn't provide a better solution to this is beyond me!Bloodstock
@SamCrawford No idea. I did this solution for a project which ended years ago.Backfire
M
26

Because I did not find solution how to avoid this stupid duplicate permission request I created swift class NavigatorGeolocation. The aim of this class is to override native JavaScript's navigator.geolocation API with custom one with 3 benefits:

  1. Frontend/JavaScript developer use navigator.geolocation API by standard way without taking care that it is overriden and uses code invocation JS --> Swift on behind
  2. Keep all logic outside of ViewController as much as possible
  3. No more ugly and stupid duplicate permission request (1st for app and 2nd for webview): enter image description here enter image description here

@AryeeteySolomonAryeetey answered some solution but it is missing my first and second benefit. In his solution frontend developer have to add to JavaScript code specific code for iOS. I do not like this ugly platform addtions - I mean JavaScript function getLocation invoked from swift which is never used by web or android platform. I have hybrid app (web/android/ios) which uses webview on ios/android and I want to have only one identical HTML5 + JavaScript code for all platforms but I do not want to use huge solutions like Apache Cordova (formerly PhoneGap).

You can easily integrate NavigatorGeolocation class to your project - just create new swift file NavigatorGeolocation.swift, copy content from my answer and in ViewController.swift add 4 lines related to var navigatorGeolocation.

I think that Google's Android is much clever than Apple's iOS because webview in Android does not bother with duplicate permission request because permission is already granted/denied by user for app. There is no additional security to ask it twice as some people defend Apple.

ViewController.swift:

import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate {

    var webView: WKWebView!
    var navigatorGeolocation = NavigatorGeolocation()

    override func loadView() {
        super.loadView()
        let webViewConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webViewConfiguration)
        webView.navigationDelegate = self
        navigatorGeolocation.setWebView(webView: webView)
        view.addSubview(webView)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "webapp")
        let request = URLRequest(url: url!)
        webView.load(request)
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        webView.evaluateJavaScript(navigatorGeolocation.getJavaScriptToEvaluate())
    }

}

NavigatorGeolocation.swift:

import CoreLocation
import WebKit

class NavigatorGeolocation: NSObject, WKScriptMessageHandler, CLLocationManagerDelegate {

    var locationManager = CLLocationManager()
    var listenersCount = 0
    var webView: WKWebView!

    override init() {
        super.init()
        locationManager.delegate = self
    }

    func setWebView(webView: WKWebView) {
        webView.configuration.userContentController.add(self, name: "listenerAdded")
        webView.configuration.userContentController.add(self, name: "listenerRemoved")
        self.webView = webView
    }

    func locationServicesIsEnabled() -> Bool {
        (CLLocationManager.locationServicesEnabled()) ? true : false
    }

    func authorizationStatusNeedRequest(status: CLAuthorizationStatus) -> Bool {
        (status == .notDetermined) ? true : false
    }

    func authorizationStatusIsGranted(status: CLAuthorizationStatus) -> Bool {
        (status == .authorizedAlways || status == .authorizedWhenInUse) ? true : false
    }

    func authorizationStatusIsDenied(status: CLAuthorizationStatus) -> Bool {
        (status == .restricted || status == .denied) ? true : false
    }

    func onLocationServicesIsDisabled() {
        webView.evaluateJavaScript("navigator.geolocation.helper.error(2, 'Location services disabled');")
    }

    func onAuthorizationStatusNeedRequest() {
        locationManager.requestWhenInUseAuthorization()
    }

    func onAuthorizationStatusIsGranted() {
        locationManager.startUpdatingLocation()
    }

    func onAuthorizationStatusIsDenied() {
        webView.evaluateJavaScript("navigator.geolocation.helper.error(1, 'App does not have location permission');")
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "listenerAdded" {
            listenersCount += 1

            if !locationServicesIsEnabled() {
                onLocationServicesIsDisabled()
            } else if authorizationStatusIsDenied(status: locationManager.authorizationStatus) {
                onAuthorizationStatusIsDenied()
            } else if authorizationStatusNeedRequest(status: locationManager.authorizationStatus) {
                onAuthorizationStatusNeedRequest()
            } else if authorizationStatusIsGranted(status: locationManager.authorizationStatus) {
                onAuthorizationStatusIsGranted()
            }
        } else if message.name == "listenerRemoved" {
            listenersCount -= 1

            // no listener left in web view to wait for position
            if listenersCount == 0 {
                locationManager.stopUpdatingLocation()
            }
        }
    }

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        // didChangeAuthorization is also called at app startup, so this condition checks listeners
        // count before doing anything otherwise app will start location service without reason
        if listenersCount > 0 {
            if authorizationStatusIsDenied(status: status) {
                onAuthorizationStatusIsDenied()
            } else if authorizationStatusIsGranted(status: status) {
                onAuthorizationStatusIsGranted()
            }
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            webView.evaluateJavaScript("navigator.geolocation.helper.success('\(location.timestamp)', \(location.coordinate.latitude), \(location.coordinate.longitude), \(location.altitude), \(location.horizontalAccuracy), \(location.verticalAccuracy), \(location.course), \(location.speed));")
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        webView.evaluateJavaScript("navigator.geolocation.helper.error(2, 'Failed to get position (\(error.localizedDescription))');")
    }

    // swiftlint:disable:next function_body_length
    func getJavaScriptToEvaluate() -> String {
        let javaScriptToEvaluate = """
            // management for success and error listeners and its calling
            navigator.geolocation.helper = {
                listeners: {},
                noop: function() {},
                id: function() {
                    var min = 1, max = 1000;
                    return Math.floor(Math.random() * (max - min + 1)) + min;
                },
                clear: function(isError) {
                    for (var id in this.listeners) {
                        if (isError || this.listeners[id].onetime) {
                            navigator.geolocation.clearWatch(id);
                        }
                    }
                },
                success: function(timestamp, latitude, longitude, altitude, accuracy, altitudeAccuracy, heading, speed) {
                    var position = {
                        timestamp: new Date(timestamp).getTime() || new Date().getTime(), // safari can not parse date format returned by swift e.g. 2019-12-27 15:46:59 +0000 (fallback used because we trust that safari will learn it in future because chrome knows that format)
                        coords: {
                            latitude: latitude,
                            longitude: longitude,
                            altitude: altitude,
                            accuracy: accuracy,
                            altitudeAccuracy: altitudeAccuracy,
                            heading: (heading > 0) ? heading : null,
                            speed: (speed > 0) ? speed : null
                        }
                    };
                    for (var id in this.listeners) {
                        this.listeners[id].success(position);
                    }
                    this.clear(false);
                },
                error: function(code, message) {
                    var error = {
                        PERMISSION_DENIED: 1,
                        POSITION_UNAVAILABLE: 2,
                        TIMEOUT: 3,
                        code: code,
                        message: message
                    };
                    for (var id in this.listeners) {
                        this.listeners[id].error(error);
                    }
                    this.clear(true);
                }
            };

            // @override getCurrentPosition()
            navigator.geolocation.getCurrentPosition = function(success, error, options) {
                var id = this.helper.id();
                this.helper.listeners[id] = { onetime: true, success: success || this.noop, error: error || this.noop };
                window.webkit.messageHandlers.listenerAdded.postMessage("");
            };

            // @override watchPosition()
            navigator.geolocation.watchPosition = function(success, error, options) {
                var id = this.helper.id();
                this.helper.listeners[id] = { onetime: false, success: success || this.noop, error: error || this.noop };
                window.webkit.messageHandlers.listenerAdded.postMessage("");
                return id;
            };

            // @override clearWatch()
            navigator.geolocation.clearWatch = function(id) {
                var idExists = (this.helper.listeners[id]) ? true : false;
                if (idExists) {
                    this.helper.listeners[id] = null;
                    delete this.helper.listeners[id];
                    window.webkit.messageHandlers.listenerRemoved.postMessage("");
                }
            };
        """

        return javaScriptToEvaluate
    }

}

EDIT 2023/09: Fix deprecation warning by changing CLLocationManager.authorizationStatus() to locationManager.authorizationStatus.

UPDATE 2021/02: I have removed useless method NavigatorGeolocation.setUserContentController() because WKWebViewConfiguration.userContentController can be added in NavigatorGeolocation.setWebView() via webView.configuration.userContentController.add() So implementation of NavigatorGeolocation in ViewController is simpler (minus one line)

Moreta answered 31/12, 2019 at 10:13 Comment(21)
This is such a good solutiono for this awkard problem! Well done :clap:Garment
btw.. these are the recomomended rules for implementing the Geolocation functionality: w3.org/TR/geolocation-APIGarment
Thanks, I have implemented it by Mozilla doc developer.mozilla.org/en-US/docs/Web/API/Geolocation_API which is simple to read. I know that my solution is not 100% replacement for native navigator.geolocation (e.g. you can see that I do not support options argument), but it is usable in 95% use cases. Geolocation options I think are rarely used, there is nothing extremely important (except for accuracy which iOS provides by default in best mode so it is not problem). Anyone can use solution as starting point/hint and fine tune it.Moreta
Copied this exactly and I still get prompted.Barranquilla
@AlexKornhauser What "promted" mean? One nice prompt from app is OK (not twice). Maybe you are doing something wrong. Debug it step by step to look for problem. You should inspect app webview via desktop safari and check if navigator.geolocation was overridden - must have helper object navigator.geolocation.helper.Moreta
I'm trying to make it so webview never asks for location, always using my phones location...Barranquilla
I dont understand. It is purpose of my work webview to not ask for permission because it is duplicated and with terrible long app path show to user. See my images in answer.Moreta
Worked perfectly. Implemented on company project. Gave all credits to you on the about page ;). I changed your implementation a bit, to fit an existing class to handle custom scripts!Weakly
@Moreta I also fetch the same issue on the simulator, but on the real device, it's working fine.Subclass
@MdImranChoudhury so you do not need this solution? Maybe some difference in apple device model/year or maybe meanwhile iOS changed this stupid behavior? Im not testing it any more since I always apply my solution. Can you post please device model and iOS version?Moreta
This works on ios13 but not on ios14, it will still ask user for location accessIcian
@John do you mean location access for webview with ugly local path?Moreta
I mean the webview popup, prior ios14beta won't get displayed and avoid redundant location access (app and webview), but ios14beta will show the webview popup as usual. This happens to me. Trying to find a workoarund/solutionIcian
To make this work I had to pass the big block of injected javascript in getJavaScripToEvaluate to the <script> tag in the HTML side because the functions were not being overriden.Valuer
@Valuer Hm but now your frontend code is not compatible outside webkit webview or you must apply that JS block only for iOS app deployment. I still did not tested so far my solution on latest iOS what is the problem.Moreta
@Moreta the frontend code is still compatible with other platforms you just have to check if webkit is available and run this.Valuer
I updated the code due to the deprecation of CLLocationManager.authorizationStatus(). I used instead: authorizationStatusIsDenied(status: locationManager.authorizationStatusBramlett
@John tested today in iOS 14.3 and latest 14.4 and it works well (no ugly duplicated permission request for webview)Moreta
@Valuer Solution in my answer worked in the time of creating my answer and works also today - I just used it today in another project. No injecting via <script> tag is needed. When in console you do not see navigator.geolocation.helper you are doing something wrong because native API is not overridden. Check if you set webView.navigationDelegate = self; and if you implemented func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) and if it is triggered - put there debug info e.g. print("didFinish navigation")Moreta
@Moreta yes I'm using the view delegate... It's not wrong if it works :) that was the only way to make it work in my implementation. Thanks for the solution.Valuer
In my case, I must call webView.evaluateJavaScript(navigatorGeolocation.getJavaScripToEvaluate()); in func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) instead of func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)Franciscofranciska
B
19

Turns out it's quite hard, but possible to do. You have to inject JavaScript code which intercepts requests to navigator.geolocation and transfer them to your app, then get the location with CLLocationManager, then inject location back to the JavaScript.

Here is the brief scheme:

  1. Add WKUserScript to your WKWebView configuration which overrides methods of navigator.geolocation. Injected JavaScript should look like this:

    navigator.geolocation.getCurrentPosition = function(success, error, options) { ... };
    navigator.geolocation.watchPosition = function(success, error, options) { ... };
    navigator.geolocation.clearWatch = function(id) { ... };
    
  2. With WKUserContentController.add(_:name:) add script message handler to your WKWebView. Injected JavaScript should call your handler, like this:

    window.webkit.messageHandlers.locationHandler.postMessage('getCurrentPosition');
    
  3. When a web page will request a location, this method will fire userContentController(_:didReceive:) so your app would know web page is requesting location. Find your location with the help of CLLocationManager as usual.

  4. Now it's time to inject the location back to the requesting JavaScript with webView.evaluateJavaScript("didUpdateLocation({coords: {latitude:55.0, longitude:0.0}, timestamp: 1494481126215.0})"). Of course your injected JavaScript should have didUpdateLocation function ready to launch saved success handler.

Quite a long algorithm, but it works!

Backfire answered 11/5, 2017 at 5:47 Comment(12)
this saved my day :)Rickets
Hi... can u provide GitHub example?Jamey
@PanMluvčí Sorry, right now I don't have time for publishing full-fledged solution. I can answer specific questions though.Backfire
Hi... any chance to show more detailed answer? I am lost at 1st markJamey
Hi @AlexanderVasenin, can you provide a detailed answer or a GitHub example is appreciatedVenomous
I dont receieve userContentController(_:didReceive:) message name and body.Iaea
@AlexanderVasenin can u please provide a sample now?Schneider
@AlexanderVasenin I just created a sample gist.github.com/hayahyts/2c369563b2e9f244356eb6228ffba261Schneider
@PanMluvčí see my answer - there is full code example and can be easily integrated (just 4 lines in ViewController)Moreta
@GisonGeorge see my answer - there is full code example and can be easily integrated (just 4 lines in ViewController)Moreta
Is this approach still the only way to avoid the double location popup with iOS 13.3? I'm trying to submit an app and it's being rejected because the second popup does not provide the reason from the NSLocationWhenInUseUsageDescription attribute in the plist. How Apple doesn't provide a better solution to this is beyond me!Bloodstock
@SamCrawford No idea. I did this solution for a project which ended years ago.Backfire
S
6

So following the steps outlined by @AlexanderVasenin, I created a gist which works perfectly.

Code Sample Here

Assuming index.html is the page you're trying to load.

  1. Override the HTML method navigator.geolocation.getCurrentPosition which is used to request for location info with this script
 let scriptSource = "navigator.geolocation.getCurrentPosition = function(success, error, options) {window.webkit.messageHandlers.locationHandler.postMessage('getCurrentPosition');};"
 let script = WKUserScript(source: scriptSource, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
 contentController.addUserScript(script)

so whenever the webpage tries to call navigator.geolocation.getCurrentPosition, we override it by calling func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)

  1. the userContentController method then gets the location data from CLLocationManager and calls a method in the webpage to handle that response. In my case, the method is getLocation(lat,lng).

This is the full code.

ViewController.swift

import UIKit
import WebKit
import CoreLocation

class ViewController: UIViewController , CLLocationManagerDelegate, WKScriptMessageHandler{
    var webView: WKWebView?
    var manager: CLLocationManager!

    override func viewDidLoad() {
        super.viewDidLoad()

        manager = CLLocationManager()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.requestAlwaysAuthorization()
        manager.startUpdatingLocation()

        let contentController = WKUserContentController()
        contentController.add(self, name: "locationHandler")

        let config = WKWebViewConfiguration()
        config.userContentController = contentController

        let scriptSource = "navigator.geolocation.getCurrentPosition = function(success, error, options) {window.webkit.messageHandlers.locationHandler.postMessage('getCurrentPosition');};"
        let script = WKUserScript(source: scriptSource, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        contentController.addUserScript(script)

        self.webView = WKWebView(frame: self.view.bounds, configuration: config)
        view.addSubview(webView!)

        webView?.uiDelegate = self
        webView?.navigationDelegate = self
        webView?.scrollView.delegate = self
        webView?.scrollView.bounces = false
        webView?.scrollView.bouncesZoom = false

        let url = Bundle.main.url(forResource: "index", withExtension:"html")
        let request = URLRequest(url: url!)

        webView?.load(request)
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "locationHandler",let  messageBody = message.body as? String {
            if messageBody == "getCurrentPosition"{
                let script =
                    "getLocation(\(manager.location?.coordinate.latitude ?? 0) ,\(manager.location?.coordinate.longitude ?? 0))"
                webView?.evaluateJavaScript(script)
            }
        }
    }
}

index.html

<!DOCTYPE html>
<html>
    <body>

        <h1>Click the button to get your coordinates.</h1>

        <button style="font-size: 60px;" onclick="getUserLocation()">Try It</button>

        <p id="demo"></p>

        <script>
            var x = document.getElementById("demo");

            function getUserLocation() {
                if (navigator.geolocation) {
                    navigator.geolocation.getCurrentPosition(showPosition);
                } else {
                    x.innerHTML = "Geolocation is not supported by this browser.";
                }
            }

        function showPosition(position) {
            getLocation(position.coords.latitude,position.coords.longitude);
        }

        function getLocation(lat,lng) {
            x.innerHTML = "Lat: " +  lat+
            "<br>Lng: " + lng;
        }
        </script>

    </body>
</html>
Schneider answered 7/7, 2019 at 10:14 Comment(3)
Hoooly moly! You made my day! I have been looking for a solution for ages! Please let me know if I can make a small donation to you or thank you in any other way!Lulualaba
[email protected] :)Schneider
It seems I got excited a bit too early, as I'm still having trouble (after implementing your code, it no longer gave me double permission requests, but what I didnt notice is that now my location scripts no longer work :(...) Can I maybe contact you on your mail address to ask for some help?Lulualaba
S
0

Based on the accepted answer I was able to make WKWebView access the user location (if you have the permission) to use on websites, sites with maps for example, on macOS (previously OSX); although this works out of the box on iOS on macOS was a totally different dance.

Using Swift 5

Create a class that implements WKScriptMessageHandler protocol. Preferably this needs to be a separated object as it will be retained by the WKUserContentController.

The method will be called when sending a message from JavaScript

final class Handler: NSObject, WKScriptMessageHandler {
    weak var web: Web?
        
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        switch message.body as? String {
            case "getCurrentPosition":

                let location = /* get user location using CoreLocation, as a CLLocation object */
                web?.evaluateJavaScript(
                            "locationReceived(\(location.coordinate.latitude), \(location.coordinate.longitude), \(location.horizontalAccuracy));")

            default: break
            }
    }
}

JavaScript that needs to get added to the user controller

let script = """
var locationSuccess = null;

function locationReceived(latitude, longitude, accuracy) {
    var position = {
        coords: {
            latitude: latitude,
            longitude: longitude,
            accuracy: accuracy
        }
    };

    if (locationSuccess != null) {
        locationSuccess(position);
    }

    locationSuccess = null;
}

navigator.geolocation.getCurrentPosition = function(success, error, options) {
    locationSuccess = success;
    window.webkit.messageHandlers.handler.postMessage('getCurrentPosition');
};

"""

Instantiate your WKWebView using your handler on the WKWebViewConfiguration and assign a weak reference from the handler to the webview

Also add the JavaScript as a user script to the WKUserContentController


let handler = Handler()
let configuration = WKWebViewConfiguration()
configuration.userContentController.add(handler, name: "handler")
configuration.userContentController.addUserScript(.init(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: true))

let webView = WKWebView(frame: .zero, configuration: configuration)
handler.web = webView
Smirk answered 11/1, 2021 at 13:28 Comment(0)
M
0

Here's a SwiftUI version for simplicity

import SwiftUI
import WebKit

struct ContentView: View {
    var body: some View {
        WebView()
    }
}

struct WebView: UIViewRepresentable {
    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        WebScriptManager.shared.config(webView)
        return webView
    }
    
    func updateUIView(_ webView: WKWebView, context: Context) {
        webView.load(URLRequest(url: URL(string: "https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API/Using_the_Geolocation_API#result")!))
    }
}
struct ScriptMessageCall {
    let name: String
    let body: String
    let callback: String
}

let GEOGetCurrentPosition = ScriptMessageCall(name: "geolocation", body: "getCurrentPosition", callback: "getCurrentPositionCallback")

class WebScriptManager: NSObject, WKScriptMessageHandler {
    static let shared = WebScriptManager()
    
    private override init() {}
    
    let injectScript = """
        navigator.geolocation.getCurrentPosition = function(success, error, options) {
          webkit.messageHandlers.\(GEOGetCurrentPosition.name).postMessage("\(GEOGetCurrentPosition.body)");
        };

        function \(GEOGetCurrentPosition.callback)(latitude, longitude) {
          console.log(`position: ${latitude}, ${longitude}`);
        };
    """

    var webView: WKWebView!
    
    func config(_ webView: WKWebView) {
        self.webView = webView
        let controller = self.webView.configuration.userContentController
        controller.addUserScript(WKUserScript(source: injectScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
        controller.add(self, name: GEOGetCurrentPosition.name)
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == GEOGetCurrentPosition.name, (message.body as? String) == GEOGetCurrentPosition.body {
            webView.evaluateJavaScript("\(GEOGetCurrentPosition.callback)(0, 0)", completionHandler: nil)
        }
    }
}

You could check the console.log via Enabling Web Inspector

Micronucleus answered 10/8, 2021 at 6:27 Comment(1)
Unfortunately your solution is not working. I always get the second message box as described in the first answer (Screenshot >>Second ugly request for webview)Nata
E
-2

I agree that when using WKWebView to create an app, it is important to avoid the double location prompt. We used the WebViewGold for iOS app template for it & it worked out of the box without second / repeated dialog.

Enyedy answered 11/8, 2022 at 10:24 Comment(1)
Its showing in spite of using WebViewGoldPyxidium

© 2022 - 2024 — McMap. All rights reserved.