如何防止WKWebView重复请求访问位置的权限?

2023-12-10

我有一个WKWebView在我的应用程序中,当我开始浏览 www.google.com 或任何其他需要位置服务的网站时,会出现一个弹出窗口,请求访问设备位置的权限,即使我已经同意共享我的位置。

我为管理这个位置所做的唯一一件事就是添加了NSLocationWhenInUseUsageDescription我的属性info.plist.

我在网上找不到任何答案,因此任何想法将不胜感激。


因为我没有找到如何避免这种愚蠢的重复权限请求的解决方案,所以我创建了 swift 类 NavigatorGeolocation。这个类的目的是覆盖原生 JavaScriptnavigator.geolocation具有自定义 API 的 API 具有 3 个优点:

  1. 前端/JavaScript 开发人员使用navigator.geolocationAPI 通过 标准方式,无需注意它被覆盖并使用代码 后面调用 JS --> Swift
  2. 尽可能将所有逻辑保留在 ViewController 之外
  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 回答了一些解决方案,但它缺少我的第一个和第二个好处。在他的解决方案中,前端开发人员必须向 JavaScript 代码添加特定于 iOS 的代码。我不喜欢这个丑陋的平台附加功能 - 我的意思是 JavaScript 函数getLocation从 swift 调用,web 或 android 平台从未使用过。我有一个混合应用程序(web/android/ios),它在 ios/android 上使用 webview,我只想为所有平台提供一个相同的 HTML5 + JavaScript 代码,但我不想使用像 Apache Cordova(以前称为 PhoneGap)这样的庞大解决方案。

您可以轻松地将 NavigatorGeolocation 类集成到您的项目中 - 只需创建新的 swift 文件 NavigatorGeolocation.swift,从我的答案中复制内容并在 ViewController.swift 中添加与 var 相关的 4 行navigatorGeolocation.

我认为 Google 的 Android 比 Apple 的 iOS 聪明得多,因为 Android 中的 webview 不会为重复的权限请求而烦恼,因为用户已经为应用程序授予/拒绝了权限。由于有些人为苹果辩护,因此两次询问并没有额外的安全保障。

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())
    }

}

导航器地理定位.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
    }

}

编辑 2023/09:通过更改修复弃用警告CLLocationManager.authorizationStatus() to locationManager.authorizationStatus.

2021/02 更新:我删除了无用的方法 NavigatorGeolocation.setUserContentController() 因为 WKWebViewConfiguration.userContentController 可以通过 webView.configuration.userContentController.add() 添加到 NavigatorGeolocation.setWebView() 中,所以在 ViewController 中实现 NavigatorGeolocation 更简单(减去一行)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何防止WKWebView重复请求访问位置的权限? 的相关文章

  • 在完成块中保留循环

    在我的课堂上 我创建了这个方法 void refreshDatasourceWithSuccess CreateDataSourceSuccessBlock successBlock failure CreateDataSourceFail
  • 如何以编程方式设置设备(UI)方向?

    希望屏幕 UI 上的所有内容都能够从横向左向右旋转 反之亦然 我该怎么做呢 这是私人的吗 我知道 BOOL shouldAutorotateToInterfaceOrientation UIInterfaceOrientation inte
  • NSCalendar 返回明年第一周上周一的错误日期

    我使用下面的代码使用随机日期来计算上周一 哪个工作文件但我的代码在明年日期中断 下面是相同的代码 NSDate date NSDate dateWithTimeIntervalSince1970 1483620311 228 NSLog c
  • 无法验证包:721772200.itmsp

    我写这篇文章是因为我有一个严重的问题 我在cartoonsmart 网站上购买了Commander Cool 游戏 然而 一个严重的问题发生了 iTune Connect 验证无法接受该申请 我尝试从两个不同的开发者帐户发布它 但错误仍然发
  • iOS Safari Mobile 禁用上一个和下一个选择输入

    上周五我发现了关于此问题的类似问题 但似乎无法再次找到它 如果有人能指出我正确的方向 那就太好了 本质上我在一个页面上有多个选择菜单 第一个在加载时填充 第二个在第一个选择时填充 够简单的 但是 在 iOS 设备中 当您点击选择元素时 它会
  • 如何在 Swift 中将文件名与文件扩展名分开?

    给定包中文件的名称 我想将该文件加载到我的 Swift 应用程序中 所以我需要使用这个方法 let soundURL NSBundle mainBundle URLForResource fname withExtension ext 无论
  • 使用 NSString 进行 UTF8 解码

    我是 Objective C 新手 尝试使用以下示例将格式错误的 UTF8 编码 NSString 转换为格式良好的字符串苹果文档 http developer apple com library mac documentation Coc
  • .showsPhysics 内存泄漏

    我最近花了 5 个小时尝试调试 Spritekit 应用程序中的内存泄漏 应用程序启动后 我注意到内存使用量略有上升 我花了 5 个小时中的 3 个小时挖掘参考资料 了解强与弱的关系ARC https developer apple com
  • 如何在iOS中处理1到3个手指的滑动手势

    我使用以下代码来处理代码中的 1 根手指滑动 UISwipeGestureRecognizer swipe UISwipeGestureRecognizer alloc initWithTarget self action selector
  • AppStore 提交:错误 ITMS-9000:“无效的捆绑结构 - 不允许二进制文件‘MyApp.app/BuildAgent’

    我陷入了以下错误 我根本不明白 错误 ITMS 9000 无效的捆绑结构 不允许使用二进制文件 MyApp app BuildAgent 您的应用程序可能只包含一个可执行文件 当我使用 Xcode 从 Archive 导出到 IPA 时 我
  • 从 UIPickerView 的选定行设置 UIButton 的标题

    详细场景是这样的 我使用循环创建 10 个按钮并设置 0 9 的标签 点击每个按钮时 我将调用 UIPickerView 在其中加载来自不同数组的数据 到这里我就得到了预期的结果 但我希望 pickerView 中选定的行应设置为相应按钮的
  • 如何从 SDK 实现每个会话的 Google Places 自动完成功能?

    是否可以从 Android 和 iOS 应用程序的 place sdk 实现基于会话的自动完成 根据 6 月 11 日生效的新 Google 地图框架定价 对自动完成的请求可以分为基于击键 会话的请求 我找不到描述实施步骤的文档 除了这个参
  • Objective-C UILabel 作为超链接

    我正在尝试做一个UILabel一个链接UIWebView 我怎样才能做一个UILabel作为超链接 您可以使用 UITapGestureRecognizer 它将实现与您想要的类似的功能 UILabel myLabel UILabel al
  • iOS 中 NSDecimalNumber 的小数分隔符错误

    我尝试通过以下方式输出具有正确的小数分隔符的十进制数的描述 NSString strValue 9 94300 NSDecimalNumber decimalNumber NSDecimalNumber decimalNumberWithS
  • Swift,以编程方式更改 UICollectionViewCell 和 UILabel(单元格内)的宽度

    我已将单元格 UICollectionViewCell 的宽度设置为等于 UICollectionView 的宽度 并且我尝试对该单元格中包含的 UILabel 执行完全相同的操作 我认为下面的代码准确地解释了我想要实现的目标 所以我在这里
  • 在 iPhone 3GS 与 iPhone 4 上为 Mobile Safari 嵌入 HTML5 视频

    我在服务器上的 mp4 文件中有 H 264 AAC 编码的视频 mime 类型的视频 mp4 添加到 Web 服务器 IIS 7 并且我有一个带有视频标签的页面
  • UIViewControllerAnimatedTransitioning:旋转更改后黑屏片段

    我已经创建了一个视图控制器转换 只要我不更改设备方向 一切都正常 图 1 显示了应有的屏幕 然后我切换到下一个视图控制器 在其中更改方向 现在我回到第一个视图控制器并再次切换方向 然后我得到的结果如图 2 所示 出现黑色边框 请不要介意屏幕
  • iOS 7 上 Safari 浏览器的用户代理

    我只想在带有 Safari 浏览器的 iPhone 和 iPod 中打开我的网站 对于 Chrome Dolphin 等任何其他浏览器 它不应该打开 但目前我从几乎所有设备获得相同的用户代理 对于Safari User Agent Stri
  • 如何在代码中编辑约束

    我有一个以 100 开始宽度限制的网页 当用户单击按钮时 我想将约束更改为 200 我试过这个 NSLayoutConstraint constrain NSLayoutConstraint constraintWithItem self
  • CoreBluetooth:检测设备超出范围/连接超时

    我正在设计一个 iOS 框架来处理多个 BLE 设备 均为同一类型 目前一切都运行良好 除了一件事 客户想要一个包含可用设备的列表 但是 我如何检测过去发现的设备何时不再可用 当我尝试连接到不再可用的设备时 会出现另一个问题 文档说 连接尝

随机推荐