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>
关键说明
- 表头注入时机:延迟0.1秒执行表头注入,确保
GMSAutocompleteViewController的子视图已完全加载,能准确找到内部的UITableView。 - 定位逻辑:通过
CLLocationManager处理权限请求和位置更新,获取坐标后调用Google Places的逆地理编码接口,将坐标转换为具体地址。 - 权限容错:覆盖了未授权、权限拒绝等场景,给出明确的用户提示。
内容的提问来源于stack exchange,提问作者user20691001
相关产品推荐
相关产品推荐

