You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 17:39:22