在不符合 MapAnnotationProtocol 的 SwiftUI 2 中添加 MapAnnotations

Posted

技术标签:

【中文标题】在不符合 MapAnnotationProtocol 的 SwiftUI 2 中添加 MapAnnotations【英文标题】:Adding MapAnnotations in SwiftUI 2 not conforming to MapAnnotationProtocol 【发布时间】:2021-08-21 02:19:27 【问题描述】:

我正在尝试将从 API 返回的所有地址添加到地图上。不幸的是,API 没有返回长/纬度坐标,但我可以从 geocodeAddressString 中检索它们,当我放入 List 时,它会正确输出。

struct Locations: Decodable 
  let _id: Int
  let streetaddress: String?
  let suburb: String?
  let state: String?
  let postcode: String?
  func getCoordinates(handler: @escaping ((CLLocationCoordinate2D) -> Void)) 
    if let address = streetaddress, let suburb = suburb, let postcode = postcode, let state = state 
    CLGeocoder().geocodeAddressString("\(address) \(suburb), \(state) \(postcode)")  ( placemark, error ) in
      handler(placemark?.first?.location?.coordinate ?? CLLocationCoordinate2D())
    
  

我有网络调用进入一个类(所以我可以使用和调用来自其他屏幕的数据):

// minimised info
final class ModelData: ObservableObject 
  @Published var locations: [ModelRecord] = []
  func getLocationData() 
    // call the network
    self.locations = locations
  

所以在我的主视图中,我有一张地图,如果我正常使用它可以正常工作(没有注释)。但是当我尝试从 getCoordinates() 函数循环注释时,它会说它不符合 - 我假设这是因为循环中的循环。

struct MapView: View 
  @StateObject var mapViewModel = MapViewModel() // loads the map init
  @StateObject var modelData = ModelData()       // loads the api data
  var body: some View 
    Map(
      coordinateRegion: $mapViewModel.region,
      interactionModes: .all,
      showsUserLocation: true,
      annotationItems: modelData.locations,
      annotationContent:  location in
        location.getCoordinates()  i in
          MapPin(coordinate: i)
        
      
    )
    .onAppear  modelData. getLocationData()  // load data
  

有没有办法解决这个问题,以便我可以在地图上显示位置?我阅读和观看的所有内容都是相反的(有坐标和获取地址名称)。

【问题讨论】:

MapAnnotationProtocol 的一致性添加到您的Locations 结构(应该称为Location,因为它代表一个位置)。您不能将异步方法作为视图的一部分来调用。您需要在模型中执行异步工作,并在拥有坐标后简单地将坐标公开为属性。 【参考方案1】:

在您的代码中,坐标仅对 MapPin 有用(否则我想您会在 Locations 结构中创建一个 coordinates 属性)。

在这种情况下,您可以创建一个Pin 结构(Identifiable 可以被Map 使用):

struct Pin: Identifiable 
    var coordinate: CLLocationCoordinate2D
    let id = UUID()

现在您的视图MapView,不再显示Locations 的数组,而是Pin 的数组。因此它的状态是[Pin] (1)。首先是空的(我们没有坐标),当我们得到 GeoCoder (2) 的结果时,它会被填充。

struct MapView: View 
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 48.862725, longitude: 2.287592), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))

    @StateObject var modelData = ModelData() // loads the api data

    @State private var pins: [Pin] = [] // (1)
    var body: some View 
        Map(
            coordinateRegion: $region,
            interactionModes: .all,
            showsUserLocation: true,
            annotationItems: pins,
            annotationContent:  pin in
            MapPin(coordinate: pin.coordinate)
            
        )
        .onAppear  modelData.getLocationData()  // load data
        .onChange(of: modelData.locations.isEmpty)  _ in
            for location in modelData.locations 
                location.getCoordinates  coordinate in
                    print("et voilà !")
                    pins.append(Pin(coordinate: coordinate)) // (2)
                
            
        
    

结构Locations 保持不变。 我用这些数据进行了测试:

final class ModelData: ObservableObject 
    @Published var locations: [Locations] = []
    func getLocationData() 
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) 
            self.locations = [.init(_id: 1, streetaddress: "11 rue Vineuse", suburb: "", state: "France", postcode: "75016"), .init(_id: 2, streetaddress: "11 rue Chardin", suburb: "", state: "France", postcode: "75016"), .init(_id: 3, streetaddress: "11 avenue Kléber", suburb: "", state: "France", postcode: "75016")]
                
    

编辑:ios15

对于第一个版本,我小心翼翼地没有修改您的模型。但我们现在可以尝试做得更好。

首先,我们将确保Map 可以直接获取Locations 的数组。我们使Locations 可识别,并为其添加coordinate 属性:

struct Locations: Decodable, Identifiable 
    let _id: Int
    let streetaddress: String?
    let suburb: String?
    let state: String?
    let postcode: String?

    var id: Int  _id 
    var coordinate: CLLocationCoordinate2D? = nil

    private enum CodingKeys: CodingKey 
        case _id, streetaddress, suburb, state, postcode
    

我们借此机会移除使用 Geocoder 的功能。相反,它在获取数据的类中占有一席之地。

所以我移动它,并借此机会使用 async/await。此版本仅适用于 Xcode13 / iOS15:

@available(iOS 15.0, *)
final class ModelData: ObservableObject 
    @Published var locations: [Locations] = []

    @MainActor
    func fetchLocationsWithCoordinates() async 
        let locations = await getLocationData()
        return await withTaskGroup(of: Locations.self)  group in
            for location in locations 
                group.async 
                    await self.updateCoordinate(of: location)
                
            
            for await location in group 
                self.locations.append(location)
            
        
    

    private func updateCoordinate(of location: Locations) async -> Locations 
        var newLoc = location
        newLoc.coordinate = try? await CLGeocoder().geocodeAddressString(
            "\(location.streetaddress ?? "") \(location.suburb ?? ""), \(location.state ?? "") \(location.postcode ?? "")"
        ).first?.location?.coordinate
        //await Task.sleep(1_000_000_000)
        return newLoc
    

    private func getLocationData() async -> [Locations] 
        //await Task.sleep(4_000_000_000)
        return [.init(_id: 1, streetaddress: "11 rue Vineuse", suburb: "", state: "France", postcode: "75016"), .init(_id: 2, streetaddress: "11 rue Chardin", suburb: "", state: "France", postcode: "75016"), .init(_id: 3, streetaddress: "11 avenue Kléber", suburb: "", state: "France", postcode: "75016")]
    

现在在View 中,我可以使用.task() 修饰符调用检索Locations 及其coordinates 的函数。

@available(iOS 15.0, *)
struct SwiftUIView15: View 
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 48.862725, longitude: 2.287592), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
    @StateObject var modelData = ModelData() // loads the api data
    var body: some View 
        Map(
            coordinateRegion: $region,
            interactionModes: .all,
            showsUserLocation: true,
            annotationItems: modelData.locations,
            annotationContent:  pin in
                MapPin(coordinate: pin.coordinate ?? CLLocationCoordinate2D())
            
        )
        .task 
            await modelData.fetchLocationsWithCoordinates()
        
    

它更干净。

【讨论】:

以上是关于在不符合 MapAnnotationProtocol 的 SwiftUI 2 中添加 MapAnnotations的主要内容,如果未能解决你的问题,请参考以下文章

在不符合 PCI 的情况下通过 PayPal 重复借记卡/信用卡付款

c#如何在不符合条件时显示错误 - 对于每个语句

如何在不重复标题的情况下创建表格?

如何在不使用 setCoordinate 的情况下更新注释?

为啥我们不能在不从 NSObject 继承类的情况下迅速采用协议?

如何在不知道 JGit 本地是不是存在的情况下签出远程分支?