|
@@ -0,0 +1,240 @@
|
|
|
|
|
+//
|
|
|
|
|
+// 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)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|