本文解决annotationView重叠时响应问题、未选中与选中状态下annotationView尺寸变化、标签变化问题。
按照官方给的api,实现简单的展示annotation本来是很简单的,但是要加一些自定义内容,就很变态了,毕竟只能继承他的AnnotationView,看不到实现源码,很多特性都要去摸索!
我们的需求是,地图上有一个人员集合,每个人有姓名-name,电话phone。要求默认状态是小头像+顶部name;点击后选中,变为大头像+顶部name&phone,选中后点击电话号码要弹出拨号AlertSheet。
刚开始我是把label和icon通过代码生成一个图片,但是发现图片中name很模糊;找网上找了不少办法还是做不出高清文字。加上做成图片,没办法区分点击了icon还是name,所以放弃了这种办法。
最后参照官方Demo,使用CustomAnnotationView。(这不能用气泡callout,因为不选中的时候也要显示一个label)
Demo中是自定义calloutView,每次需要显示、隐藏就在setSelected内removeFromSuperView,addSubView去修改UI。我是在init方法里面全部初始化UI,然后在setSelected方法内,调用自定义方法annoLayoutSubViews去修改icon大小,label的大小、label的内容,最后修改整个CustomAnnotationView大小。可能是因为修改了CustomAnnotationView的frame,导致了每次点击后,地图上被点击的annoView就消失不见了,用手势缩放地图后,它才被显示出来。于是我想了一个办法,在setSelected里面添加了一个回调selectCallback。在回调里面缩放地图。
annotationView?.zIndex = theAno.zIndex
annotationView?.selectCallback = {
annoisSelected, annoCoor in
self.zoomCount += 1
self.zoomQueue()
if annoisSelected {
self.mapCenter = annoCoor
}
}
func setMapCenterForCoor(_ coor: CLLocationCoordinate2D) {
zoomMap()
self.aMapView.setCenter(coor, animated: true)
}
func zoomMap() {
let zl = self.aMapView.zoomLevel
let defaultFocusZoom = 15.0
if zl < defaultFocusZoom {
self.aMapView.zoomLevel = defaultFocusZoom + 0.1
}else {
self.aMapView.zoomLevel = defaultFocusZoom - 0.1
}
}
//zoomQueue的目的是在短时间的,不管有多少个selectCallback,都只对地图做一次缩放
func zoomQueue() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.zoomCount -= 1
if self.zoomCount == 0 {
self.setMapCenterForCoor(self.mapCenter)
}else if self.zoomCount < 0 {
self.zoomCount = 0
}
}
}
开发中遇到另外一个问题是,每个annoView的所属区域为图2所示红色框范围。label(name+phone)之上,我添加了button,可以监测到点击事件,并进行block回调。但是点击已选中的annView的黄色区域,是没有任何响应的!点击红色区域,高德内部会执行- (void)setSelected:(BOOL)selected animated:(BOOL)animated方法。当有两个annoView重叠时,点击上方的annoView的黄色区域(透明的),下方的annoView是不会响应的!!!测试大哥就说:不行!于是我只能在annoView内重写了下面的方法:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let iconOrigin = self.iconView.frame.origin
let iconSize = self.iconView.frame.size
let suprInside = super.point(inside: point, with: event)
if isSelected {
if suprInside {
if point.x > iconOrigin.x &&
point.x < iconOrigin.x + iconSize.width &&
point.y > iconOrigin.y &&
point.y < iconOrigin.y + iconSize.height{
//点击了 icon图片
}else if point.y > iconOrigin.y {
//点击了 icon 左右两侧(黄色区域)
DispatchQueue.main.async {
if Date().timeIntervalSince(self.lastPointTestDate) > 0.1 {
debugPrint("~~~~~~点击了 icon 左右两侧~~~~~~")
self.tapInside!(point)
}
self.lastPointTestDate = Date()
}
}
}
}else {
// debugPrint("~~~~~not selected~~~~~~~")
}
return suprInside
}
再在回调方法中,重新判断到底应该选中哪一个annoView
annotationView?.tapInside = {
point in
let pointInMap = annotationView?.convert(point, to: self.aMapView)
self.selectAnnoAtPoint(point: pointInMap!, exceptAnno: theAno)
}
func selectAnnoAtPoint(point: CGPoint, exceptAnno: XXXAnotation) {
let assumeUnselectedAnnoIconSize = CGSize(width: CGFloat(36.0), height: CGFloat(53.0))
let assumeUnselectedAnnoIconY: CGFloat = 25.0
for i in 0..<anotationArray.count {
let index = anotationArray.count-i - 1
//设置了annoView.zIndex,大值在上,默认为0。
//倒叙取anno,从最上方的anno判断其对应的annoView是否处于被点击范围内
let annotation = anotationArray[index]
if annotation != exceptAnno {
let annotationCenter = aMapView.convert(annotation.coordinate, toPointTo: aMapView)
let annoIconX = annotationCenter.x - assumeUnselectedAnnoIconSize.width/2.0
let annoIconY: CGFloat = annotationCenter.y - (assumeUnselectedAnnoIconSize.width + assumeUnselectedAnnoIconY)/2.0 + assumeUnselectedAnnoIconY
if point.x > annoIconX &&
point.x < annoIconX + assumeUnselectedAnnoIconSize.width &&
point.y > annoIconY &&
point.y < annoIconY + assumeUnselectedAnnoIconSize.height {
//点击 范围在 此 anno 的 view内
self.selecetAnno(annotation)
break
}
}
}
}
func selecetAnno(_ anno: XXXAnotation) {
if aMapView.selectedAnnotations.count == 1 {
let selectedAnno = aMapView.selectedAnnotations[0]
if selectedAnno is XXXAnotation {
self.aMapView.deselectAnnotation(selectedAnno as! XXXAnotation, animated: true)
}
}
//等待zoomQueue()结束后0.3s
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
self.aMapView.selectAnnotation(anno, animated: false)
}
}
经过处理,即使点击一个已选中的annoView黄色区域,其下方的annoView仍然可以被直接点击