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

Adding MapAnnotations in SwiftUI 2 not conforming to MapAnnotationProtocol

我正在尝试将 return 从 API 编辑的所有地址添加到地图上。不幸的是,API 没有 return long/lat 坐标,但我可以从 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())
    }
  }
}

我的网络调用进入 class(因此我可以使用和调用来自其他屏幕的数据):

// 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
  }
}

有什么方法可以解决此问题,以便我可以在地图上显示位置?我读过和看过的所有内容都是相反的(有坐标和获取地址名称)。

在您的代码中,坐标仅对 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 的功能。相反,它在获取数据的 class 中占有一席之地。

于是我搬了过来,趁机使用了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()
        }
    }
}

更干净了。