在不符合 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()
}
}
}
更干净了。
我正在尝试将 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()
}
}
}
更干净了。