在不符合 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 重复借记卡/信用卡付款
如何在不使用 setCoordinate 的情况下更新注释?