// // MapAnnotationView.swift // Runner // // Created by Groot on 2025/5/9. // import UIKit import MapKit class MapAnnotationView: MKAnnotationView { static let identifier: String = "MapAnnotationView" var marker: ATMapMarker? { didSet { updateView() } } var markerImageView: MapAnnotationMarkerImageView? var textView: MapAnnotationMarkerTextView? var batteryView : MapAnnotationBatteryView? // spacing between text and image let spacing: CGFloat = 6 var annotationSelected: Bool { return (marker?.isSelected ?? false) || self.isSelected } override init(annotation: MKAnnotation?, reuseIdentifier: String?) { super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) self.marker = annotation as? ATMapMarker self.isEnabled = marker?.markerType.supportSelected ?? false self.isHidden = false } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - 视图更新与重用 override func prepareForReuse() { super.prepareForReuse() // 清除当前所有子视图 for subview in subviews { subview.removeFromSuperview() } markerImageView = nil textView = nil } private func updateView() { // 确保在更新视图前移除所有子视图 for subview in subviews { subview.removeFromSuperview() } guard let marker = marker else { return } // 创建并添加图像视图 markerImageView = MapAnnotationMarkerImageView(markerType: marker.markerType) if let url = marker.customAvatarUrl, !url.isEmpty { markerImageView?.loadNetworkImage(imageUrl: url) } if let markerImageView = markerImageView { markerImageView.isSelected = annotationSelected addSubview(markerImageView) } ///取出判断有没有用户的电池电量 let tags = marker.tags if let electricValue = tags?["electric"], let batteryPercentage = Int(electricValue), batteryPercentage > 0 { batteryView = MapAnnotationBatteryView() batteryView?.backgroundColor = UIColor(hex:"#FFFFFF") batteryView?.frame = CGRect(x: 0, y: 0, width: 53, height: 20) batteryView?.borderWidth = 1.0 batteryView?.contentPadding = 0.5 batteryView?.headSpacing = 1.0 batteryView?.batteryColor = UIColor(hex: "#4476FF") ?? UIColor.blue batteryView?.lowBatteryColor = UIColor(hex: "#FF5249") ?? UIColor.red batteryView?.lowBatteryThreshold = 30 batteryView?.showPercentageText = true batteryView?.textFont = UIFont.boldSystemFont(ofSize: 10) batteryView?.textColor = UIColor(hex: "#666666") ?? UIColor.black batteryView?.layer.cornerRadius = 10 batteryView?.layer.masksToBounds = true addSubview(batteryView ?? MapAnnotationBatteryView()) batteryView?.percentage = batteryPercentage } else { // 如果有标题,添加文本视图 if let markerName = marker.markerName, !markerName.isEmpty { textView = MapAnnotationMarkerTextView(text: markerName) if let textView = textView { addSubview(textView) } } } // 设置AutoLayout setupConstraints() } // MARK: - 布局 private func setupConstraints() { // 禁用自动约束转换 translatesAutoresizingMaskIntoConstraints = false guard let markerImageView = markerImageView else { return } markerImageView.translatesAutoresizingMaskIntoConstraints = false var markerSize = marker?.markerType.size ?? CGSize(width: 30, height: 30) if let url = marker?.customAvatarUrl, !url.isEmpty { markerSize = CGSize(width: 52, height: 52) markerImageView.layer.cornerRadius = 52 / 2 markerImageView.layer.masksToBounds = true // 确保超出部分被裁剪 } // 设置图像视图约束 - 居中显示在标记视图中 NSLayoutConstraint.activate([ markerImageView.centerXAnchor.constraint(equalTo: centerXAnchor), markerImageView.centerYAnchor.constraint(equalTo: centerYAnchor), markerImageView.widthAnchor.constraint(equalToConstant: markerSize.width), markerImageView.heightAnchor.constraint(equalToConstant: markerSize.height) ]) // 设置文本视图约束 - 位于图像上方,居中对齐 if let textView = textView { textView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ textView.centerXAnchor.constraint(equalTo: centerXAnchor), textView.bottomAnchor.constraint(equalTo: markerImageView.topAnchor, constant: -spacing) ]) } ///电池电量 if let batteryView = batteryView { batteryView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ batteryView.centerXAnchor.constraint(equalTo: centerXAnchor), batteryView.bottomAnchor.constraint(equalTo: markerImageView.topAnchor, constant: -spacing) ]) } // 设置标记视图整体尺寸 var textHeight = textView?.intrinsicContentSize.height ?? 0 var totalHeight = textView != nil ? markerSize.height + textHeight + spacing : markerSize.height if let batteryView = batteryView { textHeight = batteryView.intrinsicContentSize.height totalHeight = markerSize.height + textHeight + spacing } // 宽度至少和markerImageView一样宽,高度根据是否有文本决定 NSLayoutConstraint.activate([ widthAnchor.constraint(greaterThanOrEqualTo: markerImageView.widthAnchor), heightAnchor.constraint(equalToConstant: totalHeight) ]) // 更新偏移量 updateCenterOffset() } // MARK: - 更新视图 override func layoutSubviews() { super.layoutSubviews() updateCenterOffset() } private func updateCenterOffset() { let markerSize = marker?.markerType.size ?? CGSize(width: 30, height: 30) if let textView = textView { let textHeight = textView.frame.height let totalHeight = markerSize.height + textHeight + spacing // 有文本时,调整偏移让图片底部对准坐标点 centerOffset = CGPoint(x: 0, y: -(totalHeight - markerSize.height)/2) } else if let batteryView = batteryView { let textHeight = batteryView.frame.height let totalHeight = markerSize.height + textHeight + spacing // 有文本时,调整偏移让图片底部对准坐标点 centerOffset = CGPoint(x: 0, y: -(totalHeight - markerSize.height)/2) } else { // 没有文本时,仅需要将图片底部对准坐标点 centerOffset = CGPoint(x: 0, y: -markerSize.height/2) } } override func prepareForDisplay() { super.prepareForDisplay() // 确保视图已经设置好 if markerImageView == nil { updateView() } // 更新选中状态 guard let marker = marker, let url = marker.customAvatarUrl, !url.isEmpty else { markerImageView?.isSelected = annotationSelected return } } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) marker?.isSelected = selected guard let marker = marker, let url = marker.customAvatarUrl, !url.isEmpty else { markerImageView?.isSelected = annotationSelected return } } }