// // MapAnotationBubbleView.swift // map_mapkit_ios // // Created by 诺诺诺的言 on 2025/7/16. // import MapKit class MapAnotationBubbleView : MKAnnotationView { static let identifier: String = "MapAnotationBubbleView" var marker: ATMapMarker? { didSet { updateView() } } private var allContentView : UIView! private var markerContentView : UIView! private var bgImageView : UIImageView! private var titleLabelView: UILabel! private var bottomView : UIView! private var triangleLayer: CAShapeLayer! private var bubbleHeight: CGFloat = 24 private var triangleHeight: CGFloat = 6 // 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() } markerContentView = nil markerContentView = nil } private func updateView() { // 确保在更新视图前移除所有子视图 for subview in subviews { subview.removeFromSuperview() } guard let marker = marker else { return } let tags = marker.tags let mainTiel : String = tags?["title"] ?? ""; let mainDesc : String = tags?["desc"] ?? ""; let markerContentViewW = 170 markerContentView = UIView() markerContentView.backgroundColor = UIColor(hex: "#FFFFFF") markerContentView.layer.cornerRadius = 12 markerContentView.layer.masksToBounds = true markerContentView.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.08).cgColor markerContentView.layer.shadowOpacity = 1 markerContentView.layer.shadowRadius = 4 markerContentView.layer.shadowOffset = CGSize(width: 0, height: 4) addSubview(markerContentView) titleLabelView = UILabel() titleLabelView.translatesAutoresizingMaskIntoConstraints = false // 关键 titleLabelView.text = mainTiel titleLabelView.font = UIFont.systemFont(ofSize: 10, weight: .bold) titleLabelView.textColor = UIColor(hex: "#333333") titleLabelView.backgroundColor = .clear titleLabelView.textAlignment = .left titleLabelView.numberOfLines = 0 markerContentView.addSubview(titleLabelView) bottomView = UIView() bottomView.translatesAutoresizingMaskIntoConstraints = false // 关键 markerContentView.addSubview(bottomView) let mapIconView = UIImageView(frame: CGRect(x: 0, y: 0, width: 12, height: 12)) mapIconView.image = readImageContentFrom(imageName: "com.shishi.dingwei_map_pin.png") mapIconView.contentMode = .scaleAspectFit bottomView.addSubview(mapIconView) let mapLableW = markerContentViewW - 16 - 14 let mapLable = UILabel(frame: CGRect(x: 14, y: 0, width: mapLableW, height: 14)) mapLable.text = mainDesc mapLable.font = UIFont.systemFont(ofSize: 10, weight: .regular) mapLable.textColor = UIColor(hex: "#666666") mapLable.backgroundColor = .clear mapLable.textAlignment = .left mapLable.lineBreakMode = .byTruncatingTail mapLable.numberOfLines = 1 // 限制为单行 bottomView.addSubview(mapLable) // 创建气泡三角形 triangleLayer = CAShapeLayer() triangleLayer.fillColor = UIColor.white.cgColor let trianglePath = UIBezierPath() trianglePath.move(to: CGPoint(x: 0, y: 0)) // 左点 trianglePath.addLine(to: CGPoint(x: 5, y: 6)) // 底点 trianglePath.addLine(to: CGPoint(x: 10, y: 0)) // 右点 trianglePath.close() triangleLayer.path = trianglePath.cgPath layer.addSublayer(triangleLayer) // 设置AutoLayout setupConstraints() } // MARK: - 布局 private func setupConstraints() { // 禁用自动约束转换 translatesAutoresizingMaskIntoConstraints = false guard let marker = marker else { return } let tags = marker.tags let mainTiel : String = tags?["title"] ?? ""; let markerContentViewW : CGFloat = 170 let mainTitleH : CGFloat = mainTiel.height(withConstrainedWidth: CGFloat(markerContentViewW - 16), font: UIFont.systemFont(ofSize: 10, weight: .bold)) let markerContentViewH : CGFloat = CGFloat(mainTitleH) + 8 + 14 + 5 + 8 bubbleHeight = markerContentViewH; //let markerSize = CGSize(width: markerContentViewW, height: markerContentViewH) // 设置图像视图约束 - 居中显示在标记视图中 guard let markerContentView = markerContentView else { return } markerContentView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ markerContentView.centerXAnchor.constraint(equalTo: centerXAnchor), markerContentView.centerYAnchor.constraint(equalTo: centerYAnchor), markerContentView.widthAnchor.constraint(equalToConstant: markerContentViewW), markerContentView.heightAnchor.constraint(equalToConstant: markerContentViewH) ]) // 标题(动态高度) NSLayoutConstraint.activate([ titleLabelView.topAnchor.constraint(equalTo: markerContentView.topAnchor, constant: 8), titleLabelView.leadingAnchor.constraint(equalTo: markerContentView.leadingAnchor, constant: 8), titleLabelView.trailingAnchor.constraint(equalTo: markerContentView.trailingAnchor, constant: -8), titleLabelView.heightAnchor.constraint(equalToConstant: mainTitleH) // 移除固定高度约束,让标题根据内容自动调整 ]) // 底部区域(动态高度) NSLayoutConstraint.activate([ bottomView.topAnchor.constraint(equalTo: titleLabelView.bottomAnchor, constant: 5), bottomView.leadingAnchor.constraint(equalTo: markerContentView.leadingAnchor, constant: 8), bottomView.trailingAnchor.constraint(equalTo: markerContentView.trailingAnchor, constant: -8), bottomView.bottomAnchor.constraint(equalTo: markerContentView.bottomAnchor, constant: -8) // 移除固定高度约束,让底部视图根据内容自动调整 ]) // 设置整个视图的高度(包含三角形)和最小宽度 NSLayoutConstraint.activate([ heightAnchor.constraint(equalToConstant: bubbleHeight + 6), widthAnchor.constraint(greaterThanOrEqualToConstant: markerContentViewW) ]) layoutIfNeeded() updateTrianglePosition() // 更新偏移量 //updateCenterOffset() } private func readImageContentFrom(imageName : String) -> UIImage { if let image = UIImage(named: imageName) { return image } // 尝试从插件资源束加载 let bundleURL = Bundle(for: MapAnnotationView.self).url(forResource: "map_mapkit_ios_resources", withExtension: "bundle") if let bundleURL = bundleURL, let resourceBundle = Bundle(url: bundleURL) { return UIImage(named: imageName, in: resourceBundle, compatibleWith: nil) ?? UIImage() } return UIImage() } // MARK: - 更新视图 private func updateCenterOffset() { let markerSize = CGSize(width: bubbleHeight + 6, height: 30) // 没有文本时,仅需要将图片底部对准坐标点 centerOffset = CGPoint(x: 0, y: -markerSize.height/2) } override func prepareForDisplay() { super.prepareForDisplay() // 确保视图已经设置好 } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } override func layoutSubviews() { super.layoutSubviews() updateTrianglePosition() updateCenterOffset() } private func updateTrianglePosition() { // 更新三角形位置到底部中心 let width = bounds.width triangleLayer.frame = CGRect(x: (width - 10) / 2, y: bubbleHeight + 3, width: 10, height: triangleHeight) } }