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

SwiftUI中为GMSAutocompleteViewController添加「当前位置」按钮

在SwiftUI的Google Places自动完成选择器顶部添加「使用当前位置」按钮

我现有一个可正常运行的Google Places自动完成选择器,基于SwiftUI的UIViewControllerRepresentable实现了PlacePicker组件。希望在自动完成列表顶部添加「使用当前位置」按钮,但未找到适配SwiftUI的实现方案,附上现有代码,寻求可行的实现方法。

struct PlacePicker: UIViewControllerRepresentable {
        
    func makeCoordinator() -> GooglePlacesCoordinator {
        GooglePlacesCoordinator(self)
    }
    @Environment(\.presentationMode) var presentationMode
    @Binding var address: String
    @Binding var latitude: Double
    @Binding var longitude: Double
    
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<PlacePicker>) -> GMSAutocompleteViewController {
        
        GMSPlacesClient.provideAPIKey("xxxxxxxxxxxxxxxxxxxxx")

        let autocompleteController = GMSAutocompleteViewController()
        autocompleteController.delegate = context.coordinator
        

        let fields: GMSPlaceField = GMSPlaceField(rawValue:UInt(GMSPlaceField.name.rawValue) |
                                                                  UInt(GMSPlaceField.placeID.rawValue) |
                                                                  UInt(GMSPlaceField.coordinate.rawValue) |
                                                                  GMSPlaceField.addressComponents.rawValue |
                                                                  GMSPlaceField.formattedAddress.rawValue)
        autocompleteController.placeFields = fields

        let filter = GMSAutocompleteFilter()
        filter.type = .address
        autocompleteController.autocompleteFilter = filter
        return autocompleteController
    }

    func updateUIViewController(_ uiViewController: GMSAutocompleteViewController, context: UIViewControllerRepresentableContext<PlacePicker>) {
    }

    class GooglePlacesCoordinator: NSObject, UINavigationControllerDelegate, GMSAutocompleteViewControllerDelegate {

        var parent: PlacePicker

        init(_ parent: PlacePicker) {
            self.parent = parent
        }

        func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
            DispatchQueue.main.async {
                print(place.description.description as Any)
                self.parent.address =  place.name!
                self.parent.presentationMode.wrappedValue.dismiss()
                print("latitude: \(place.coordinate.latitude)")
                print("longitude: \(place.coordinate.longitude)")
                self.parent.latitude=place.coordinate.latitude
                self.parent.longitude=place.coordinate.longitude
            }
        }

        func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
            print("Error: ", error.localizedDescription)
        }

        func wasCancelled(_ viewController: GMSAutocompleteViewController) {
            parent.presentationMode.wrappedValue.dismiss()
        }

    }
}

实现方案

要在GMSAutocompleteViewController的列表顶部添加自定义按钮,核心思路是找到自动完成列表的UITableView,插入自定义表头视图,同时处理定位权限和当前位置获取逻辑。

步骤1:修改PlacePicker代码

以下是完整的修改后的代码,包含表头注入、定位权限处理和逆地理编码逻辑:

import SwiftUI
import GooglePlaces
import CoreLocation

struct PlacePicker: UIViewControllerRepresentable {
    func makeCoordinator() -> GooglePlacesCoordinator {
        GooglePlacesCoordinator(self)
    }
    
    @Environment(\.presentationMode) var presentationMode
    @Binding var address: String
    @Binding var latitude: Double
    @Binding var longitude: Double
    
    // 定位管理器,用于获取当前位置
    private let locationManager = CLLocationManager()

    func makeUIViewController(context: UIViewControllerRepresentableContext<PlacePicker>) -> GMSAutocompleteViewController {
        GMSPlacesClient.provideAPIKey("xxxxxxxxxxxxxxxxxxxxx")
        
        let autocompleteController = GMSAutocompleteViewController()
        autocompleteController.delegate = context.coordinator
        
        let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
                                                      UInt(GMSPlaceField.placeID.rawValue) |
                                                      UInt(GMSPlaceField.coordinate.rawValue) |
                                                      UInt(GMSPlaceField.addressComponents.rawValue) |
                                                      UInt(GMSPlaceField.formattedAddress.rawValue))!
        autocompleteController.placeFields = fields
        
        let filter = GMSAutocompleteFilter()
        filter.type = .address
        autocompleteController.autocompleteFilter = filter
        
        // 请求定位权限
        locationManager.requestWhenInUseAuthorization()
        locationManager.delegate = context.coordinator
        
        // 延迟注入表头,确保列表视图已加载
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            addCurrentLocationHeader(to: autocompleteController, coordinator: context.coordinator)
        }
        
        return autocompleteController
    }
    
    func updateUIViewController(_ uiViewController: GMSAutocompleteViewController, context: UIViewControllerRepresentableContext<PlacePicker>) {}
    
    // 构建自定义表头按钮
    private func addCurrentLocationHeader(to controller: GMSAutocompleteViewController, coordinator: GooglePlacesCoordinator) {
        // 遍历子视图找到自动完成列表的UITableView
        guard let tableView = controller.view.subviews.first(where: { $0 is UITableView }) as? UITableView else { return }
        
        let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 50))
        headerView.backgroundColor = .systemBackground
        
        let currentLocationButton = UIButton(type: .system)
        currentLocationButton.frame = headerView.bounds.inset(by: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16))
        currentLocationButton.setTitle("使用当前位置", for: .normal)
        currentLocationButton.setImage(UIImage(systemName: "location.fill"), for: .normal)
        currentLocationButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)
        currentLocationButton.addTarget(coordinator, action: #selector(coordinator.fetchCurrentLocation), for: .touchUpInside)
        
        headerView.addSubview(currentLocationButton)
        tableView.tableHeaderView = headerView
    }
    
    class GooglePlacesCoordinator: NSObject, UINavigationControllerDelegate, GMSAutocompleteViewControllerDelegate, CLLocationManagerDelegate {
        var parent: PlacePicker
        private let placesClient = GMSPlacesClient.shared()
        
        init(_ parent: PlacePicker) {
            self.parent = parent
        }
        
        // 获取当前位置并逆地理编码
        @objc func fetchCurrentLocation() {
            switch CLLocationManager.authorizationStatus() {
            case .authorizedWhenInUse, .authorizedAlways:
                parent.locationManager.startUpdatingLocation()
            case .notDetermined:
                parent.locationManager.requestWhenInUseAuthorization()
            default:
                // 权限不足时提示用户
                let alert = UIAlertController(title: "定位权限不足", message: "请在设置中开启定位权限以使用该功能", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "确定", style: .default))
                if let topVC = UIApplication.shared.windows.first?.rootViewController?.presentedViewController {
                    topVC.present(alert, animated: true)
                }
            }
        }
        
        // 定位更新回调
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            guard let location = locations.last else { return }
            manager.stopUpdatingLocation()
            
            // 根据坐标获取地址信息
            placesClient.lookUpPlaceID(from: location.coordinate) { [weak self] place, error in
                guard let self = self, let place = place, error == nil else {
                    print("获取当前位置地址失败: \(error?.localizedDescription ?? "未知错误")")
                    return
                }
                
                DispatchQueue.main.async {
                    self.parent.address = place.formattedAddress ?? place.name ?? ""
                    self.parent.latitude = place.coordinate.latitude
                    self.parent.longitude = place.coordinate.longitude
                    self.parent.presentationMode.wrappedValue.dismiss()
                }
            }
        }
        
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            print("定位失败: \(error.localizedDescription)")
        }
        
        // 原有自动完成回调
        func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
            DispatchQueue.main.async {
                self.parent.address = place.formattedAddress ?? place.name ?? ""
                self.parent.latitude = place.coordinate.latitude
                self.parent.longitude = place.coordinate.longitude
                self.parent.presentationMode.wrappedValue.dismiss()
            }
        }
        
        func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
            print("自动完成错误: ", error.localizedDescription)
        }
        
        func wasCancelled(_ viewController: GMSAutocompleteViewController) {
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
}

步骤2:配置Info.plist权限

在项目的Info.plist中添加定位权限描述,否则无法正常请求权限:

<key>NSLocationWhenInUseUsageDescription</key>
<string>需要获取您的当前位置来快速选择地址</string>

关键说明

  1. 表头注入时机:延迟0.1秒执行表头注入,确保GMSAutocompleteViewController的子视图已完全加载,能准确找到内部的UITableView。
  2. 定位逻辑:通过CLLocationManager处理权限请求和位置更新,获取坐标后调用Google Places的逆地理编码接口,将坐标转换为具体地址。
  3. 权限容错:覆盖了未授权、权限拒绝等场景,给出明确的用户提示。

内容的提问来源于stack exchange,提问作者user20691001

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 22:05:16