如何聚焦 Google 使用 swiftUI 视图在启动时将相机映射到用户的当前位置
How to focus Google Maps camera on user's current location at startup using swiftUI views
我正在尝试制作一个使用 Google 地图并在应用打开时关注用户位置的应用。
现在我正在初始化地图,在按下 Google 地图固有的 'myLocation' 按钮后我无法聚焦在用户位置上,但是地图的相机一直聚焦到指定的位置位置而不是用户位置。
我使用这 2 个教程到达了现在的位置:
- https://developers.google.com/maps/documentation/ios-sdk/start
- https://www.raywenderlich.com/197-google-maps-ios-sdk-tutorial-getting-started
在搜索 Google 和此处之后,似乎我需要利用 CLLocationManager() 来获取用户的设备坐标,然后以某种方式使用它?我认为我关于 CLLocationManager() 的代码可能放在错误的文件中或使用不当,但我没有收到任何错误。
我的代码是这样工作的:SceneDelegate.swift 将我的 LandmarkList.swift 设置为 rootViewController。然后 LandmarkList 调用 GoogMapView.swift 来显示 Google 地图的实例。
SceneDelegate.swift:
我想我在这里对locationManager的使用可能是错误的?
import UIKit
import SwiftUI
import GoogleMaps
import GooglePlaces
import CoreLocation
class SceneDelegate: UIResponder, UIWindowSceneDelegate, CLLocationManagerDelegate {
var window: UIWindow?
private let locationManager = CLLocationManager()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Use a UIHostingController as window root view controller
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: LandmarkList())
self.window = window
window.makeKeyAndVisible()
}
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}
}
}
LandmarkList.swift:
import SwiftUI
struct LandmarkList: View {
@State private var searchText = ""
@State private var locationText = ""
var body: some View {
ZStack(alignment: Alignment.top) {
GoogMapView()
.frame(height: 750)
SlideOverCard {
VStack(alignment: .leading) {
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
}
}
}
}
GoogMapView.swift:
注意:下面的打印语句只有returns'用户位置未知'
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
struct GoogMapView : UIViewRepresentable {
let marker : GMSMarker = GMSMarker()
//Creates a `UIView` instance to be presented.
func makeUIView(context: Context) -> GMSMapView {
// Create a GMSCameraPosition
let camera = GMSCameraPosition.camera(withLatitude: 42.361145, longitude: -71.057083, zoom: 16.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
if let mylocation = mapView.myLocation {
print("User's location: \(mylocation)")
} else {
print("User's location is unknown")
}
return mapView
}
// Updates the presented `UIView` (and coordinator) to the latestconfiguration.
func updateUIView(_ mapView: GMSMapView, context: Context) {
// Creates a marker in the center of the map.
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
}
}
同样,我认为我在 SceneDelegate.swift 中关于 locationManager 的代码会使 GoogleMaps 相机的实例在启动时聚焦于用户位置,但它不会。
有人知道我做错了什么吗?
您可以通过 "pretending" 您的视图是 ViewController 来实现。尝试将 GoogleMapView
设置为代表。然后把你的初始化代码放在makeUIView
然后遵守协议:
struct GoogMapView : CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status == .authorizedWhenInUse else {
return
}
locationManager.startUpdatingLocation()
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
locationManager.stopUpdatingLocation()
}
}
以上摘自https://www.raywenderlich.com/197-google-maps-ios-sdk-tutorial-getting-started。
使用此处和 Whosebug 上的其他评论,我制定了这个解决方案:
//
// GoogMapView.swift
// Landmarks
//
// Created by Zahr Lyttle on 10/14/19.
// Copyright © 2019 Apple. All rights reserved.
//
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Foundation
struct GoogMapView: View {
var body: some View {
GoogMapControllerRepresentable()
}
}
class GoogMapController: UIViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
var mapView: GMSMapView!
let defaultLocation = CLLocation(latitude: 42.361145, longitude: -71.057083)
var zoomLevel: Float = 15.0
let marker : GMSMarker = GMSMarker()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude, longitude: defaultLocation.coordinate.longitude, zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
// if let mylocation = mapView.myLocation {
// print("User's location: \(mylocation)")
// } else {
// print("User's location is unknown")
// }
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
// Add the map to the view, hide it until we've got a location update.
view.addSubview(mapView)
// mapView.isHidden = true
}
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)
if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera
} else {
mapView.animate(to: camera)
}
}
// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
}
struct GoogMapControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<GMControllerRepresentable>) -> GMController {
return GMController()
}
func updateUIViewController(_ uiViewController: GMController, context: UIViewControllerRepresentableContext<GMControllerRepresentable>) {
}
}
我终于弄清楚了如何立即关注 "my location",而不是要求用户输入。这里有更多详细信息...希望对您有所帮助!
updateUIView
- 您希望在检索到用户位置时调用
updateUIView
。根据我的阅读,当 @State
或 @Binding
对象更改(或 @ObservedObject
)时,将调用 updateUIView
。因此,UIViewRepresentable
需要其中之一。对于下面的代码,我使用 @ObservedObject
。每当 @ObservedObject
的 @Published
属性之一发生变化时,将调用 updateUIView
。
创建观察对象
我创建了一个符合ObservableObject协议的class(LocationManager
)。
在class中,我将lastKnownLocation
暴露为@Published
属性。如上所述,当 lastKnownLocation
更新时,LocationManager
class 实例的任何订阅者都将看到这些更新
获取用户坐标
CLLocationManager
用于获取用户位置的更新。
我创建的LocationManager
class实现了CLLocationManager
的委托函数
更新用户位置时调用委托函数didUpdateLocations
。在该函数中,我然后更新 @Published
属性 以便 UIViewRepresentable
看到这些更改,并且相应地自动调用 updateUIView
。
UIViewRepresentable
import SwiftUI
import GoogleMaps
import Combine
struct HomeView: UIViewRepresentable {
// Listen to changes on the locationManager
@ObservedObject var locationManager = LocationManager()
func makeUIView(context: Self.Context) -> GMSMapView {
// Just default the camera to anywhere (this will be overwritten as soon as myLocation is grabbed
let camera = GMSCameraPosition.camera(withLatitude: 0, longitude: 0, zoom: 16.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Self.Context) {
// When the locationManager publishes updates, respond to them
if let myLocation = locationManager.lastKnownLocation {
mapView.animate(toLocation: myLocation.coordinate)
print("User's location: \(myLocation)")
}
}
}
LocationManager
class:
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
// Publish the user's location so subscribers can react to updates
@Published var lastKnownLocation: CLLocation? = nil
private let manager = CLLocationManager()
override init() {
super.init()
self.manager.delegate = self
self.manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
self.manager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Notify listeners that the user has a new location
self.lastKnownLocation = locations.last
}
}
我正在尝试制作一个使用 Google 地图并在应用打开时关注用户位置的应用。
现在我正在初始化地图,在按下 Google 地图固有的 'myLocation' 按钮后我无法聚焦在用户位置上,但是地图的相机一直聚焦到指定的位置位置而不是用户位置。
我使用这 2 个教程到达了现在的位置: - https://developers.google.com/maps/documentation/ios-sdk/start - https://www.raywenderlich.com/197-google-maps-ios-sdk-tutorial-getting-started
在搜索 Google 和此处之后,似乎我需要利用 CLLocationManager() 来获取用户的设备坐标,然后以某种方式使用它?我认为我关于 CLLocationManager() 的代码可能放在错误的文件中或使用不当,但我没有收到任何错误。
我的代码是这样工作的:SceneDelegate.swift 将我的 LandmarkList.swift 设置为 rootViewController。然后 LandmarkList 调用 GoogMapView.swift 来显示 Google 地图的实例。
SceneDelegate.swift:
我想我在这里对locationManager的使用可能是错误的?
import UIKit
import SwiftUI
import GoogleMaps
import GooglePlaces
import CoreLocation
class SceneDelegate: UIResponder, UIWindowSceneDelegate, CLLocationManagerDelegate {
var window: UIWindow?
private let locationManager = CLLocationManager()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Use a UIHostingController as window root view controller
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: LandmarkList())
self.window = window
window.makeKeyAndVisible()
}
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}
}
}
LandmarkList.swift:
import SwiftUI
struct LandmarkList: View {
@State private var searchText = ""
@State private var locationText = ""
var body: some View {
ZStack(alignment: Alignment.top) {
GoogMapView()
.frame(height: 750)
SlideOverCard {
VStack(alignment: .leading) {
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
}
}
}
}
GoogMapView.swift:
注意:下面的打印语句只有returns'用户位置未知'
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
struct GoogMapView : UIViewRepresentable {
let marker : GMSMarker = GMSMarker()
//Creates a `UIView` instance to be presented.
func makeUIView(context: Context) -> GMSMapView {
// Create a GMSCameraPosition
let camera = GMSCameraPosition.camera(withLatitude: 42.361145, longitude: -71.057083, zoom: 16.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
if let mylocation = mapView.myLocation {
print("User's location: \(mylocation)")
} else {
print("User's location is unknown")
}
return mapView
}
// Updates the presented `UIView` (and coordinator) to the latestconfiguration.
func updateUIView(_ mapView: GMSMapView, context: Context) {
// Creates a marker in the center of the map.
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
}
}
同样,我认为我在 SceneDelegate.swift 中关于 locationManager 的代码会使 GoogleMaps 相机的实例在启动时聚焦于用户位置,但它不会。
有人知道我做错了什么吗?
您可以通过 "pretending" 您的视图是 ViewController 来实现。尝试将 GoogleMapView
设置为代表。然后把你的初始化代码放在makeUIView
然后遵守协议:
struct GoogMapView : CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status == .authorizedWhenInUse else {
return
}
locationManager.startUpdatingLocation()
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
locationManager.stopUpdatingLocation()
}
}
以上摘自https://www.raywenderlich.com/197-google-maps-ios-sdk-tutorial-getting-started。
使用此处和 Whosebug 上的其他评论,我制定了这个解决方案:
//
// GoogMapView.swift
// Landmarks
//
// Created by Zahr Lyttle on 10/14/19.
// Copyright © 2019 Apple. All rights reserved.
//
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Foundation
struct GoogMapView: View {
var body: some View {
GoogMapControllerRepresentable()
}
}
class GoogMapController: UIViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
var mapView: GMSMapView!
let defaultLocation = CLLocation(latitude: 42.361145, longitude: -71.057083)
var zoomLevel: Float = 15.0
let marker : GMSMarker = GMSMarker()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude, longitude: defaultLocation.coordinate.longitude, zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
// if let mylocation = mapView.myLocation {
// print("User's location: \(mylocation)")
// } else {
// print("User's location is unknown")
// }
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
// Add the map to the view, hide it until we've got a location update.
view.addSubview(mapView)
// mapView.isHidden = true
}
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)
if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera
} else {
mapView.animate(to: camera)
}
}
// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
}
struct GoogMapControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<GMControllerRepresentable>) -> GMController {
return GMController()
}
func updateUIViewController(_ uiViewController: GMController, context: UIViewControllerRepresentableContext<GMControllerRepresentable>) {
}
}
我终于弄清楚了如何立即关注 "my location",而不是要求用户输入。这里有更多详细信息...希望对您有所帮助!
updateUIView
- 您希望在检索到用户位置时调用
updateUIView
。根据我的阅读,当@State
或@Binding
对象更改(或@ObservedObject
)时,将调用updateUIView
。因此,UIViewRepresentable
需要其中之一。对于下面的代码,我使用@ObservedObject
。每当@ObservedObject
的@Published
属性之一发生变化时,将调用updateUIView
。
创建观察对象
我创建了一个符合ObservableObject协议的class(
LocationManager
)。在class中,我将
lastKnownLocation
暴露为@Published
属性。如上所述,当lastKnownLocation
更新时,LocationManager
class 实例的任何订阅者都将看到这些更新
获取用户坐标
CLLocationManager
用于获取用户位置的更新。我创建的
LocationManager
class实现了CLLocationManager
的委托函数
更新用户位置时调用委托函数
didUpdateLocations
。在该函数中,我然后更新@Published
属性 以便UIViewRepresentable
看到这些更改,并且相应地自动调用updateUIView
。
UIViewRepresentable
import SwiftUI
import GoogleMaps
import Combine
struct HomeView: UIViewRepresentable {
// Listen to changes on the locationManager
@ObservedObject var locationManager = LocationManager()
func makeUIView(context: Self.Context) -> GMSMapView {
// Just default the camera to anywhere (this will be overwritten as soon as myLocation is grabbed
let camera = GMSCameraPosition.camera(withLatitude: 0, longitude: 0, zoom: 16.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Self.Context) {
// When the locationManager publishes updates, respond to them
if let myLocation = locationManager.lastKnownLocation {
mapView.animate(toLocation: myLocation.coordinate)
print("User's location: \(myLocation)")
}
}
}
LocationManager
class:
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
// Publish the user's location so subscribers can react to updates
@Published var lastKnownLocation: CLLocation? = nil
private let manager = CLLocationManager()
override init() {
super.init()
self.manager.delegate = self
self.manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
self.manager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Notify listeners that the user has a new location
self.lastKnownLocation = locations.last
}
}