在您的代码中,坐标仅对 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()
}
}
}
它更干净。