Swift报错CLLocationDegrees?不符合CVarArg预期类型解决方案
报错原因
你遇到的Argument type 'CLLocationDegrees?' (aka 'Optional<Double>') does not conform to expected type 'CVarArg'错误,核心原因是调用String(format:)时传入了可选类型值:String(format:)要求传入的参数必须是非可选、遵循CVarArg协议的类型,你代码里location?.coordinate.latitude得到的是可选值,无法直接作为格式化参数传入。
除此之外你的现有代码还有两个逻辑问题:
- 对可选值使用强制解包
!,定位返回空值时会直接触发崩溃 - 定位服务和WebView实例没有打通,位置更新后根本无法调用到WebView的
updateLocation方法执行JS注入
修复步骤
- 给定位服务类增加位置更新回调属性,定位拿到有效坐标后通过回调向外抛出经纬度,避免类之间循环引用
- 处理定位回调时先做可选绑定,拿到非空的
CLLocation实例后再取经纬度,从根源上消灭可选类型参数问题,同时移除强制解包 - 在WebView初始化时给定位服务设置回调,拿到经纬度后转成字符串调用JS注入方法
- 优化JS拼接逻辑,避免字符串拼接导致的JS语法错误,增加执行结果回调方便排查问题
修复后完整代码
// // WebView.swift // ferocity // // Created by Jens Buwalda on 2022-06-17 // import Foundation import SwiftUI import WebKit import CoreLocation struct WebView : UIViewRepresentable { let url: URL var webView = WKWebView() var manager = LocationManagerService() func makeUIView(context: Context) -> WKWebView { let request = URLRequest(url: url) webView.load(request) // 设置定位回调,拿到坐标后注入JS manager.onLocationUpdate = { [weak webView] lat, lon in guard let webView = webView else { return } let latStr = String(format: "%.6f", lat) let lonStr = String(format: "%.6f", lon) webView.evaluateJavaScript("deviceLocation(\(latStr), \(lonStr));") { result, error in if let error = error { print("JS注入失败: \(error.localizedDescription)") } } } return webView } func updateUIView(_ uiView: WKWebView, context: Context) { uiView.navigationDelegate = context.coordinator } func makeCoordinator() -> Coordinator { Coordinator(self) } // 实现WKNavigationDelegate协议 class Coordinator: NSObject, WKNavigationDelegate { var parent: WebView init(_ parent: WebView) { self.parent = parent } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { // 空值校验,移除强制解包崩溃风险 guard let requestUrl = navigationAction.request.url else { decisionHandler(.allow) return } let token = getQueryStringParameter(url: requestUrl.absoluteString, param: "token") let defaults = UserDefaults.standard if let token = token{ defaults.set(token, forKey: "token") } // 读取本地存储的token自动拼接 if let memoryToken = defaults.string(forKey: "token") { print("Memory token: " + memoryToken) guard let reloadUrl = insertQueryItems(url: requestUrl.absoluteString, key: "token", value: memoryToken) else { decisionHandler(.allow) return } print("Reload url: " + reloadUrl.absoluteString) // 地址不一致时触发重定向 if requestUrl.absoluteString != reloadUrl.absoluteString{ print("Reload") webView.load(URLRequest(url: reloadUrl)) decisionHandler(.cancel) return } } else { print("No token in memory") } // SSO页面样式调整逻辑 if requestUrl.absoluteString.contains("sso"){ // TODO: 增加顶部内边距 } decisionHandler(.allow) } func getQueryStringParameter(url: String, param: String) -> String? { guard let url = URLComponents(string: url) else { return nil } return url.queryItems?.first(where: { $0.name == param })?.value } func insertQueryItems(url: String, key: String, value: String) -> URL?{ var urlObject = URLComponents(string: url) var urlComponents = urlObject?.queryItems if(urlComponents == nil){ urlComponents = [] } urlComponents?.removeAll(where: { $0.name == key }) urlComponents?.append(URLQueryItem(name: key, value: value)) urlObject?.queryItems = urlComponents return urlObject?.url } } } class LocationManagerService: NSObject, ObservableObject, CLLocationManagerDelegate { var manager: CLLocationManager = CLLocationManager() @Published var location: CLLocation? @Published var enabled: Bool = false // 位置更新回调 var onLocationUpdate: ((Double, Double) -> Void)? override init() { super.init() manager.delegate = self manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters // 按需设置定位精度,降低功耗 if CLLocationManager.locationServicesEnabled() { manager.requestWhenInUseAuthorization() manager.startUpdatingLocation() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { print("location changed") // 可选绑定拿到有效坐标,彻底解决可选类型传参问题 guard let location = locations.first else { return } self.location = location let userLat = location.coordinate.latitude let userLon = location.coordinate.longitude // 非可选类型经纬度通过回调抛出 onLocationUpdate?(userLat, userLon) } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("定位失败: \(error.localizedDescription)") } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { let status = manager.authorizationStatus enabled = (status == .authorizedWhenInUse || status == .authorizedAlways) // 权限通过后重新启动定位 if enabled { manager.startUpdatingLocation() } } }
额外优化点
- 补全了导航代理里的空值判断,移除了强制解包
navigationAction.request.url!的崩溃风险 - 修正了token重定向逻辑里的决策处理,触发重定向时直接返回
.cancel避免重复加载请求 - 给定位服务增加了精度设置、权限变更后的定位重启逻辑,减少不必要的功耗
- JS注入增加了错误回调,定位坐标注入失败时可以直接在控制台看到错误信息
内容的提问来源于stack exchange,提问作者Jens
相关产品推荐
相关产品推荐

