Swift MapKit注解不加载,直到地图点击



我正在向地图添加一堆注释,当用户移动并移动到不同的国家时,我删除注释并添加更多注释。我面临的问题是,新的注释不会显示,直到我与地图交互,无论是点击,缩放,平移或缩放。

我试过将map.addAnnotations()放入DispatchQueue中,但那不起作用,我也将内置方法loadNewCountry(country: String)偏移到dispatchGroup中。这些都没用!

注意:我有几千种不同类型的注释,所以将它们全部加载到内存中不会在旧设备上工作:)

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {

checkIfLoadNewCountry()
}
func checkIfLoadNewCountry() {
let visible  = map.centerCoordinate
geocode(latitude: visible.latitude, longitude: visible.longitude) { placemark, error in
if let error = error {
print("(error)")
return
} else if let placemark = placemark?.first {
if let isoCountry = placemark.isoCountryCode?.lowercased() {
self.loadNewCountry(with: isoCountry)
}
}
}
}
func loadNewCountry(with country: String) {
let annotationsArray = [
self.viewModel1.array,
self.viewModel2.array,
self.viewModel3.array
] as [[MKAnnotation]]

let annotations = map.annotations

autoreleasepool {
annotations.forEach {
if !($0 is CustomAnnotationOne), !($0 is CustomAnnotationTwo) {
self.map.removeAnnotation($0)
}
}
}

let group = DispatchGroup()
let queue = DispatchQueue(label: "reload-annotations", attributes: .concurrent)

group.enter()
queue.async {
self.viewModel1.load(country: country)
group.leave()
}

group.enter()
queue.async {
self.viewModel2.load(country: country)
group.leave()
}

group.wait()

DispatchQueue.main.async {
for annoArray in annotationsArray {
self.map.addAnnotations(annoArray)
}
}

}

关键问题是代码是初始化[[MKAnnotation]]与当前视图模型的结果,然后启动load的视图模型模型的一个新的country,然后添加旧的视图模型注释到地图视图。

相反,在重新加载完成后,抓取[[MKAnnotation]]:
func loadNewCountry(with country: String) {
let annotations = map.annotations
annotations
.filter { !($0 is CustomAnnotationOne || $0 is CustomAnnotationTwo || $0 is MKUserLocation) }
.forEach { map.removeAnnotation($0) }
let group = DispatchGroup()
let queue = DispatchQueue(label: "reload-annotations", attributes: .concurrent)
queue.async(group: group) {
self.viewModel1.load(country: country)
}
queue.async(group: group) {
self.viewModel2.load(country: country)
}
group.notify(queue: .main) {
let annotationsArrays: [[MKAnnotation]] = [
self.viewModel1.array,
self.viewModel2.array,
self.viewModel3.array
]
for annotations in annotationsArrays {
self.map.addAnnotations(annotations)
}
}
}

与手头的问题无关,我还有:

  • 简化了DispatchGroup组语法;
  • 取消了wait,因为你不应该阻塞主线程;
  • 消除不必要的autoreleasepool;
  • MKUserLocation添加到要排除的注释类型中(即使你现在没有显示用户位置,你可能会在将来的某个日期)…你永远不要手动删除MKUserLocation,否则你会得到奇怪的用户体验;
  • 重命名annotationArrays,以明确您正在处理数组的数组。

说句题外话,上面的代码引起了线程安全问题。您似乎正在后台队列上更新视图模型。如果您在其他地方与这些视图模型交互,请确保同步您的访问。而且,除此之外,"视图模型"的激励思想(例如,与"呈现者"模式相反)是您将它们连接起来,以便它们通知视图更改本身。

所以,你可以考虑:

  • 给视图模型异步startLoad方法;
  • 给视图模型一些机制,在加载完成时通知视图(在主队列上)变化(无论是观察者,委托协议,闭包等)。
  • 确保视图模型与其属性(例如,array)同步交互。

。,让我们假设视图模型正在通过闭包更新视图:

typealias AnnotationBlock = ([MKAnnotation]) -> Void
protocol CountryLoader {
var didAdd: AnnotationBlock? { get set }
var didRemove: AnnotationBlock? { get set }
}
class ViewModel1: CountryLoader {
var array: [CustomAnnotationX] = []
var didAdd: AnnotationBlock?
var didRemove: AnnotationBlock?
func startLoad(country: String, completion: (() -> Void)? = nil) {
DispatchQueue.global().async {
let newArray: [CustomAnnotationX] = ...   // computationally expensive load process here (on background queue)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.didRemove?(self.array)           // tell view what was removed
self.array = newArray                 // update model on main queue
self.didAdd?(newArray)                // tell view what was added
completion?()                         // tell caller that we're done
}
}
}
}

这是一个线程安全的实现,它将视图和视图控制器从任何复杂的异步进程中抽象出来。然后视图控制器需要配置视图模型:

class ViewController: UIViewController {
@IBOutlet weak var map: MKMapView!
let viewModel1 = ViewModel1()
let viewModel2 = ViewModel2()
let viewModel3 = ViewModel3()
override func viewDidLoad() {
super.viewDidLoad()
configureViewModels()
}
func configureViewModels() {
viewModel1.didRemove = { [weak self] annotations in
self?.map?.removeAnnotations(annotations)
}
viewModel1.didAdd = { [weak self] annotations in
self?.map?.addAnnotations(annotations)
}
...
}
}

然后," reload for country "变成:

func loadNewCountry(with country: String) {
viewModel1.startLoad(country: country)
viewModel2.startLoad(country: country)
viewModel3.startLoad(country: country)
}

func loadNewCountry(with country: String) {
showLoadingIndicator()
let group = DispatchGroup()
group.enter()
viewModel1.startLoad(country: country) {
group.leave()
}
group.enter()
viewModel2.startLoad(country: country) {
group.leave()
}
group.enter()
viewModel3.startLoad(country: country) {
group.leave()
}
group.notify(queue: .main) { [weak self] in
self?.hideLoadingIndicator()
}
}

这只是一种模式。根据您如何实现视图模型,实现细节可能会有很大的不同。但是我们的想法是你应该:

  • 确保视图模型是线程安全的;
  • 将复杂的线程逻辑从视图中抽象出来,保留在视图模型中;和
  • 有一些过程,其中视图模型通知视图相关的更改。

最新更新