在地图上展示用户位置 SwiftUI

8

我正在尝试加载地图,并将初始位置设置为用户位置,同时在地图上显示用户位置,这需要使用SwiftUI实现。但我不知道如何在SwiftUI中实现。

我尝试在updateUIView函数中添加“view.showsUserLocation = true”,但它没有起作用。

import SwiftUI
import MapKit
struct MapView : UIViewRepresentable {
    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        view.showsUserLocation = true
        let coordinate = CLLocationCoordinate2D(
            latitude: 34.011286, longitude: -116.166868)
        let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)

        let region = MKCoordinateRegion(center: view.userLocation.location?.coordinate ?? coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}

当我尝试打印位置时,它返回了空值(nil)。


你尝试在真实设备上测试过吗?模拟器在涉及“核心位置(Core Location)”时可能会很棘手 - 同时确保当前方案允许位置模拟:当前方案->编辑->运行->选项->允许位置模拟 - Matteo Pacini
是的,我已经做到了,并确保我已输入位置。我还在移动设备上进行了测试。我也已在info.plist中添加了权限。 - MRF
8个回答

6
请在 Info.plist 中添加键值对 Privacy - Location When In Use Usage Description 并填写描述信息。
接着,尝试更新下面的函数:
func updateUIView(_ view: MKMapView, context: Context) {

    view.showsUserLocation = true

    // Ask for Authorisation from the User.
    self.locationManager.requestAlwaysAuthorization()

    // For use in foreground
    self.locationManager.requestWhenInUseAuthorization()

    if CLLocationManager.locationServicesEnabled() {
        //        self.locationManager.delegate = self
         self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
         self.locationManager.startUpdatingLocation()

        //Temporary fix: App crashes as it may execute before getting users current location
        //Try to run on device without DispatchQueue

        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
            let locValue:CLLocationCoordinate2D = self.locationManager.location!.coordinate
            print("CURRENT LOCATION = \(locValue.latitude) \(locValue.longitude)")

            let coordinate = CLLocationCoordinate2D(
                latitude: locValue.latitude, longitude: locValue.longitude)
            let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
            let region = MKCoordinateRegion(center: coordinate, span: span)
            view.setRegion(region, animated: true)

        })
    }

}

谢谢Harshal!这很好用。但是我只能在调度下运行它。我不确定这是否是一个好的实践。 - MRF
1
谢谢Harshal。我注意到你的代码也可以放在makeUIView方法中而不是updateUIView中,这样它只会在创建时执行一次,而不是继续居中于用户的位置。感谢您的帖子! - Eric Wo

2

上述代码由于不当的权限请求会导致应用程序崩溃。使用以下代码可以获得更好的性能和及时的权限。

struct MapView: UIViewRepresentable {
    let locationManager = CLLocationManager()        
    
    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }
    
    func updateUIView(_ view: MKMapView, context: Context) {
        view.showsUserLocation = true
        let status = CLLocationManager.authorizationStatus()
        locationManager.requestAlwaysAuthorization()
        locationManager.requestWhenInUseAuthorization()
        
        if status == .authorizedAlways || status == .authorizedWhenInUse {
            locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
            locationManager.startUpdatingLocation()
            let location: CLLocationCoordinate2D = locationManager.location!.coordinate
            let span = MKCoordinateSpan(latitudeDelta: 0.009, longitudeDelta: 0.009)
            let region = MKCoordinateRegion(center: location, span: span)
            view.setRegion(region, animated: true)
        }
    }
    
}

当locationManager在updateUIView函数内被定义时,对话框会很快消失。 - Volkan Paksoy
工作得很好,但地图不会始终保持在标记上。有没有办法始终将标记保持在中心位置? - Ryan
@VolkanPaksoy,听起来这个解决方案存在问题,因为CLLocationManager实例在方法级别上被实例化,而不是在具有更长生命周期的范围内(比如类本身)。当方法结束时,locationManager引用不再需要,并且locationManager实例会被引用计数器清除。类似的问题已经在这个问题中得到了回答: https://dev59.com/mGsz5IYBdhLWcg3wcndb - Salem
@VolkanPaksoy 通过改变locationManager的范围,问题得以解决。 - Ali Pishvaee

2
这是我发现的设置CoreLocation和MapView最干净的方法。首先,您必须创建一个UIViewRepresentable,在SwiftUI中使用CoreLocation。
不要忘记在Info.plist文件中启用此隐私并添加以下信息:
隐私-始终和在使用时使用位置描述
隐私-仅在使用时使用位置描述
隐私-始终使用位置描述
import SwiftUI
import MapKit

// MARK: Struct that handle the map
struct MapView: UIViewRepresentable {

  @Binding var locationManager: CLLocationManager
  @Binding var showMapAlert: Bool

  let map = MKMapView()

  ///Creating map view at startup
  func makeUIView(context: Context) -> MKMapView {
    locationManager.delegate = context.coordinator
    return map
  }

  func updateUIView(_ view: MKMapView, context: Context) {
    map.showsUserLocation = true
    map.userTrackingMode = .follow
  }

  ///Use class Coordinator method
  func makeCoordinator() -> MapView.Coordinator {
    return Coordinator(mapView: self)
  }

  //MARK: - Core Location manager delegate
  class Coordinator: NSObject, CLLocationManagerDelegate {

    var mapView: MapView

    init(mapView: MapView) {
      self.mapView = mapView
    }

    ///Switch between user location status
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
      switch status {
      case .restricted:
        break
      case .denied:
        mapView.showMapAlert.toggle()
        return
      case .notDetermined:
        mapView.locationManager.requestWhenInUseAuthorization()
        return
      case .authorizedWhenInUse:
        return
      case .authorizedAlways:
        mapView.locationManager.allowsBackgroundLocationUpdates = true
        mapView.locationManager.pausesLocationUpdatesAutomatically = false
        return
      @unknown default:
        break
      }
      mapView.locationManager.startUpdatingLocation()
    }
   }
  }
 }

以下是翻译的结果:

这是我在SwiftUI视图中使用它的方式。如果权限被Coordinator类切换拒绝,警报将被切换:

import SwiftUI
import CoreLocation

// MARK: View that shows map to users
struct HomeView: View {

  @State var locationManager = CLLocationManager()
  @State var showMapAlert = false

  var body: some View {
    MapView(locationManager: $locationManager, showMapAlert: $showMapAlert)
        .alert(isPresented: $showMapAlert) {
          Alert(title: Text("Location access denied"),
                message: Text("Your location is needed"),
                primaryButton: .cancel(),
                secondaryButton: .default(Text("Settings"),
                                          action: { self.goToDeviceSettings() }))
    }
  }
}

extension HomeView {
  ///Path to device settings if location is disabled
  func goToDeviceSettings() {
    guard let url = URL.init(string: UIApplication.openSettingsURLString) else { return }
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
  }
}

1
尝试这个:
struct OriginMap : UIViewRepresentable {




 func makeUIView(context: UIViewRepresentableContext<OriginMap>) -> MKMapView{
    MKMapView()
}




 func updateUIView(_ mapView: MKMapView, context: Context) {
    let locationManager = CLLocationManager()


    mapView.showsUserLocation = true

    locationManager.requestAlwaysAuthorization()

    locationManager.requestWhenInUseAuthorization()

    if CLLocationManager.locationServicesEnabled() {
        locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locationManager.startUpdatingLocation()

        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
            let locValue:CLLocationCoordinate2D = locationManager.location!.coordinate


            let coordinate = CLLocationCoordinate2D(
                latitude: locValue.latitude, longitude: locValue.longitude)
            Datas.currentLocation = coordinate


            let span = MKCoordinateSpan(latitudeDelta: 0.002, longitudeDelta: 0.002)
            let region = MKCoordinateRegion(center: coordinate, span: span)
            mapView.setRegion(region, animated: true)


        })
    }}

这一行对我不起作用:Datas.currentLocation = coordinate。这里的“Datas”是什么? - Volkan Paksoy

0
import SwiftUI
import MapKit

struct ContentView: View {
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: -23.506403, longitude: -46.1509555),
        span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))

    var body: some View {
        Map(coordinateRegion: $region, showsUserLocation: true)
    }
}

0

这是SwiftUI2的代码

import MapKit
import SwiftUI

struct MapView: View {
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.4353, longitude: 35.23442), span: MKCoordinateSpan(latitudeDelta: 0.15, longitudeDelta: 0.15))
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

您还可以查看URL,了解有关 SwiftUI2 添加的其他有用功能的更多信息。


-1

使用它你可以找到用户的地区

Text(Locale.current.localizedString(forRegionCode: Locale.current.regionCode!) ?? "")
                    .font(.system(size: 30))
                    .fontWeight(.bold)

不可以。这并没有回答 OP 的问题。 - Yohst

-1

适用于 XCode 12

import SwiftUI
import MapKit

struct ContentView: View {
    
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 37.321127, longitude: -122.047790),
        span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
    
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

问题是关于显示用户位置,而不仅仅是地图。 - Ludyem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接