MapAnotationBubbleView.swift 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. //
  2. // MapAnotationBubbleView.swift
  3. // map_mapkit_ios
  4. //
  5. // Created by 诺诺诺的言 on 2025/7/16.
  6. //
  7. import MapKit
  8. class MapAnotationBubbleView : MKAnnotationView {
  9. static let identifier: String = "MapAnotationBubbleView"
  10. var marker: ATMapMarker? {
  11. didSet {
  12. updateView()
  13. }
  14. }
  15. private var allContentView : UIView!
  16. private var markerContentView : UIView!
  17. private var bgImageView : UIImageView!
  18. private var titleLabelView: UILabel!
  19. private var bottomView : UIView!
  20. private var triangleLayer: CAShapeLayer!
  21. private var bubbleHeight: CGFloat = 24
  22. private var triangleHeight: CGFloat = 6
  23. // spacing between text and image
  24. let spacing: CGFloat = 6
  25. var annotationSelected: Bool {
  26. return (marker?.isSelected ?? false) || self.isSelected
  27. }
  28. override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
  29. super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
  30. self.marker = annotation as? ATMapMarker
  31. self.isEnabled = marker?.markerType.supportSelected ?? false
  32. self.isHidden = false
  33. }
  34. required init?(coder aDecoder: NSCoder) {
  35. fatalError("init(coder:) has not been implemented")
  36. }
  37. // MARK: - 视图更新与重用
  38. override func prepareForReuse() {
  39. super.prepareForReuse()
  40. // 清除当前所有子视图
  41. for subview in subviews {
  42. subview.removeFromSuperview()
  43. }
  44. markerContentView = nil
  45. markerContentView = nil
  46. }
  47. private func updateView() {
  48. // 确保在更新视图前移除所有子视图
  49. for subview in subviews {
  50. subview.removeFromSuperview()
  51. }
  52. guard let marker = marker else { return }
  53. let tags = marker.tags
  54. let mainTiel : String = tags?["title"] ?? "";
  55. let mainDesc : String = tags?["desc"] ?? "";
  56. let markerContentViewW = 170
  57. markerContentView = UIView()
  58. markerContentView.backgroundColor = UIColor(hex: "#FFFFFF")
  59. markerContentView.layer.cornerRadius = 12
  60. markerContentView.layer.masksToBounds = true
  61. markerContentView.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.08).cgColor
  62. markerContentView.layer.shadowOpacity = 1
  63. markerContentView.layer.shadowRadius = 4
  64. markerContentView.layer.shadowOffset = CGSize(width: 0, height: 4)
  65. addSubview(markerContentView)
  66. titleLabelView = UILabel()
  67. titleLabelView.translatesAutoresizingMaskIntoConstraints = false // 关键
  68. titleLabelView.text = mainTiel
  69. titleLabelView.font = UIFont.systemFont(ofSize: 10, weight: .bold)
  70. titleLabelView.textColor = UIColor(hex: "#333333")
  71. titleLabelView.backgroundColor = .clear
  72. titleLabelView.textAlignment = .left
  73. titleLabelView.numberOfLines = 0
  74. markerContentView.addSubview(titleLabelView)
  75. bottomView = UIView()
  76. bottomView.translatesAutoresizingMaskIntoConstraints = false // 关键
  77. markerContentView.addSubview(bottomView)
  78. let mapIconView = UIImageView(frame: CGRect(x: 0, y: 0, width: 12, height: 12))
  79. mapIconView.image = readImageContentFrom(imageName: "com.shishi.dingwei_map_pin.png")
  80. mapIconView.contentMode = .scaleAspectFit
  81. bottomView.addSubview(mapIconView)
  82. let mapLableW = markerContentViewW - 16 - 14
  83. let mapLable = UILabel(frame: CGRect(x: 14, y: 0, width: mapLableW, height: 14))
  84. mapLable.text = mainDesc
  85. mapLable.font = UIFont.systemFont(ofSize: 10, weight: .regular)
  86. mapLable.textColor = UIColor(hex: "#666666")
  87. mapLable.backgroundColor = .clear
  88. mapLable.textAlignment = .left
  89. mapLable.lineBreakMode = .byTruncatingTail
  90. mapLable.numberOfLines = 1 // 限制为单行
  91. bottomView.addSubview(mapLable)
  92. // 创建气泡三角形
  93. triangleLayer = CAShapeLayer()
  94. triangleLayer.fillColor = UIColor.white.cgColor
  95. let trianglePath = UIBezierPath()
  96. trianglePath.move(to: CGPoint(x: 0, y: 0)) // 左点
  97. trianglePath.addLine(to: CGPoint(x: 5, y: 6)) // 底点
  98. trianglePath.addLine(to: CGPoint(x: 10, y: 0)) // 右点
  99. trianglePath.close()
  100. triangleLayer.path = trianglePath.cgPath
  101. layer.addSublayer(triangleLayer)
  102. // 设置AutoLayout
  103. setupConstraints()
  104. }
  105. // MARK: - 布局
  106. private func setupConstraints() {
  107. // 禁用自动约束转换
  108. translatesAutoresizingMaskIntoConstraints = false
  109. guard let marker = marker else { return }
  110. let tags = marker.tags
  111. let mainTiel : String = tags?["title"] ?? "";
  112. let markerContentViewW : CGFloat = 170
  113. let mainTitleH : CGFloat = mainTiel.height(withConstrainedWidth: CGFloat(markerContentViewW - 16), font: UIFont.systemFont(ofSize: 10, weight: .bold))
  114. let markerContentViewH : CGFloat = CGFloat(mainTitleH) + 8 + 14 + 5 + 8
  115. bubbleHeight = markerContentViewH;
  116. //let markerSize = CGSize(width: markerContentViewW, height: markerContentViewH)
  117. // 设置图像视图约束 - 居中显示在标记视图中
  118. guard let markerContentView = markerContentView else { return }
  119. markerContentView.translatesAutoresizingMaskIntoConstraints = false
  120. NSLayoutConstraint.activate([
  121. markerContentView.centerXAnchor.constraint(equalTo: centerXAnchor),
  122. markerContentView.centerYAnchor.constraint(equalTo: centerYAnchor),
  123. markerContentView.widthAnchor.constraint(equalToConstant: markerContentViewW),
  124. markerContentView.heightAnchor.constraint(equalToConstant: markerContentViewH)
  125. ])
  126. // 标题(动态高度)
  127. NSLayoutConstraint.activate([
  128. titleLabelView.topAnchor.constraint(equalTo: markerContentView.topAnchor, constant: 8),
  129. titleLabelView.leadingAnchor.constraint(equalTo: markerContentView.leadingAnchor, constant: 8),
  130. titleLabelView.trailingAnchor.constraint(equalTo: markerContentView.trailingAnchor, constant: -8),
  131. titleLabelView.heightAnchor.constraint(equalToConstant: mainTitleH)
  132. // 移除固定高度约束,让标题根据内容自动调整
  133. ])
  134. // 底部区域(动态高度)
  135. NSLayoutConstraint.activate([
  136. bottomView.topAnchor.constraint(equalTo: titleLabelView.bottomAnchor, constant: 5),
  137. bottomView.leadingAnchor.constraint(equalTo: markerContentView.leadingAnchor, constant: 8),
  138. bottomView.trailingAnchor.constraint(equalTo: markerContentView.trailingAnchor, constant: -8),
  139. bottomView.bottomAnchor.constraint(equalTo: markerContentView.bottomAnchor, constant: -8)
  140. // 移除固定高度约束,让底部视图根据内容自动调整
  141. ])
  142. // 设置整个视图的高度(包含三角形)和最小宽度
  143. NSLayoutConstraint.activate([
  144. heightAnchor.constraint(equalToConstant: bubbleHeight + 6),
  145. widthAnchor.constraint(greaterThanOrEqualToConstant: markerContentViewW)
  146. ])
  147. layoutIfNeeded()
  148. updateTrianglePosition()
  149. // 更新偏移量
  150. //updateCenterOffset()
  151. }
  152. private func readImageContentFrom(imageName : String) -> UIImage {
  153. if let image = UIImage(named: imageName) {
  154. return image
  155. }
  156. // 尝试从插件资源束加载
  157. let bundleURL = Bundle(for: MapAnnotationView.self).url(forResource: "map_mapkit_ios_resources", withExtension: "bundle")
  158. if let bundleURL = bundleURL, let resourceBundle = Bundle(url: bundleURL) {
  159. return UIImage(named: imageName, in: resourceBundle, compatibleWith: nil) ?? UIImage()
  160. }
  161. return UIImage()
  162. }
  163. // MARK: - 更新视图
  164. private func updateCenterOffset() {
  165. let markerSize = CGSize(width: bubbleHeight + 6, height: 30)
  166. // 没有文本时,仅需要将图片底部对准坐标点
  167. centerOffset = CGPoint(x: 0, y: -markerSize.height/2)
  168. }
  169. override func prepareForDisplay() {
  170. super.prepareForDisplay()
  171. // 确保视图已经设置好
  172. }
  173. override func setSelected(_ selected: Bool, animated: Bool) {
  174. super.setSelected(selected, animated: animated)
  175. }
  176. override func layoutSubviews() {
  177. super.layoutSubviews()
  178. updateTrianglePosition()
  179. updateCenterOffset()
  180. }
  181. private func updateTrianglePosition() {
  182. // 更新三角形位置到底部中心
  183. let width = bounds.width
  184. triangleLayer.frame = CGRect(x: (width - 10) / 2, y: bubbleHeight + 3, width: 10, height: triangleHeight)
  185. }
  186. }