jsdw_ios/QuickLocation/Section/Map/MemberAnnotationView.swift

270 lines
8.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MemberAnnotationView.swift
// QuickLocation
//
import Foundation
#if !targetEnvironment(simulator)
import AMapNaviKit
final class MemberAnnotationView: MAAnnotationView {
///
var onTap: ((MemberAnnotation) -> Void)?
// MARK: - Design Constants
private static let bubbleSize: CGFloat = 56
private static let bubbleCorner: CGFloat = 14
private static let borderWidth: CGFloat = 4
private static let pointerWidth: CGFloat = 14
private static let pointerHeight: CGFloat = 6
private static let gapBubbleToDot: CGFloat = 4
private static let gapNameToBubble: CGFloat = 4
private static let nameTagHeight: CGFloat = 20
private static let nameTagHPadding: CGFloat = 8
private static let dotOuterSize: CGFloat = 12
private static let dotInnerSize: CGFloat = 10
private static let currentUserDotColor = UIColor(hexStr: "#5ED4FF")
private static let memberDotPalette: [UIColor] = [
UIColor(hexStr: "#FF6B6B"),
UIColor(hexStr: "#FF9F43"),
UIColor(hexStr: "#FECA57"),
UIColor(hexStr: "#1DD1A1"),
UIColor(hexStr: "#54A0FF"),
UIColor(hexStr: "#5F27CD"),
UIColor(hexStr: "#FF9FF3"),
UIColor(hexStr: "#00D2D3"),
UIColor(hexStr: "#EE5A24"),
UIColor(hexStr: "#10AC84"),
UIColor(hexStr: "#2E86DE"),
UIColor(hexStr: "#341F97"),
]
private var showsNameTag = false
private let containerView = UIView()
private let nameTagView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.cornerRadius = 8
v.layer.shadowColor = UIColor.black.cgColor
v.layer.shadowOffset = CGSize(width: 0, height: 1)
v.layer.shadowRadius = 2
v.layer.shadowOpacity = 0.12
v.isHidden = true
return v
}()
private let nameLabel: UILabel = {
let l = UILabel()
l.font = UIFont(name: "PingFangSC-Medium", size: 11)
?? UIFont.systemFont(ofSize: 11, weight: .medium)
l.textColor = UIColor(hexStr: "#0F2846")
l.textAlignment = .center
l.lineBreakMode = .byTruncatingTail
return l
}()
private let bubbleView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.cornerRadius = bubbleCorner
v.layer.shadowColor = UIColor.black.cgColor
v.layer.shadowOffset = CGSize(width: 0, height: 2)
v.layer.shadowRadius = 4
v.layer.shadowOpacity = 0.18
return v
}()
private let avatarImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.backgroundColor = UIColor(hexStr: "#E0E0E0")
iv.layer.cornerRadius = bubbleCorner - borderWidth * 0.6
return iv
}()
private let pointerView: UIImageView = {
let iv = UIImageView(image: UIImage(named: "Map/arrow"))
iv.contentMode = .scaleAspectFit
return iv
}()
private let dotOuterView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.cornerRadius = dotOuterSize / 2
v.layer.shadowColor = UIColor.black.cgColor
v.layer.shadowOffset = CGSize(width: 0, height: 1)
v.layer.shadowRadius = 2
v.layer.shadowOpacity = 0.2
return v
}()
private let dotInnerView: UIView = {
let v = UIView()
v.layer.cornerRadius = dotInnerSize / 2
return v
}()
private let dotCenterView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.cornerRadius = 2
return v
}()
// MARK: - Init
override init!(annotation: MAAnnotation!, reuseIdentifier: String!) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
canShowCallout = false
isEnabled = true
isUserInteractionEnabled = true
addSubview(containerView)
containerView.isUserInteractionEnabled = true
containerView.addSubview(nameTagView)
nameTagView.addSubview(nameLabel)
containerView.addSubview(bubbleView)
bubbleView.addSubview(avatarImageView)
containerView.addSubview(pointerView)
containerView.addSubview(dotOuterView)
dotOuterView.addSubview(dotInnerView)
dotInnerView.addSubview(dotCenterView)
}
// MARK: - Configure
func configure(with member: CircleMember) {
isEnabled = true
isUserInteractionEnabled = true
canShowCallout = false
if let img = UIImage(named: "UserIcon/\(member.avatar)") {
avatarImageView.image = img
} else {
avatarImageView.image = UIImage(named: "Common/default_avatar")
}
avatarImageView.backgroundColor = member.avatar.isEmpty ? .lightGray : .clear
dotInnerView.backgroundColor = Self.dotColor(for: member)
showsNameTag = !member.isCurrentUser
nameLabel.text = member.name
nameTagView.isHidden = !showsNameTag
//
if gestureRecognizers?.isEmpty ?? true {
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
addGestureRecognizer(tap)
}
layoutMarker()
}
@objc private func handleTap() {
guard let ann = annotation as? MemberAnnotation else { return }
onTap?(ann)
}
/// API HomeViewController
func updateHeading(_ heading: Double) {}
// MARK: - Layout
private func layoutMarker() {
let bubble = Self.bubbleSize
let pointerH = Self.pointerHeight
let gap = Self.gapBubbleToDot
let dot = Self.dotOuterSize
let nameH: CGFloat = showsNameTag ? Self.nameTagHeight : 0
let nameGap: CGFloat = showsNameTag ? Self.gapNameToBubble : 0
var nameTagWidth: CGFloat = 0
if showsNameTag {
let text = nameLabel.text ?? ""
let textWidth = (text as NSString).size(withAttributes: [.font: nameLabel.font as Any]).width
nameTagWidth = min(max(textWidth + Self.nameTagHPadding * 2, 44), 120)
}
let totalWidth = max(bubble, nameTagWidth)
let totalHeight = nameH + nameGap + bubble + pointerH + gap + dot
bounds = CGRect(x: 0, y: 0, width: totalWidth, height: totalHeight)
containerView.frame = bounds
// annotation y
centerOffset = CGPoint(x: 0, y: -(totalHeight / 2 - Self.dotOuterSize / 2))
if showsNameTag {
nameTagView.frame = CGRect(
x: (totalWidth - nameTagWidth) / 2,
y: 0,
width: nameTagWidth,
height: Self.nameTagHeight
)
nameLabel.frame = nameTagView.bounds.insetBy(dx: Self.nameTagHPadding - 2, dy: 0)
}
let bubbleY = nameH + nameGap
bubbleView.frame = CGRect(
x: (totalWidth - bubble) / 2,
y: bubbleY,
width: bubble,
height: bubble
)
let inner = bubble - Self.borderWidth * 2
avatarImageView.frame = CGRect(
x: Self.borderWidth, y: Self.borderWidth,
width: inner, height: inner
)
//
let pointerTop = bubbleY + bubble - 1
pointerView.frame = CGRect(
x: (totalWidth - Self.pointerWidth) / 2,
y: pointerTop,
width: Self.pointerWidth,
height: Self.pointerHeight
)
let dotY = bubbleY + bubble + Self.pointerHeight + gap
dotOuterView.frame = CGRect(
x: (totalWidth - dot) / 2, y: dotY,
width: dot, height: dot
)
let inset = (Self.dotOuterSize - Self.dotInnerSize) / 2
dotInnerView.frame = CGRect(
x: inset, y: inset,
width: Self.dotInnerSize, height: Self.dotInnerSize
)
let centerSize: CGFloat = 4
let centerInset = (Self.dotInnerSize - centerSize) / 2
dotCenterView.frame = CGRect(
x: centerInset, y: centerInset,
width: centerSize, height: centerSize
)
}
// MARK: - Dot Color
private static func dotColor(for member: CircleMember) -> UIColor {
if member.isCurrentUser {
return currentUserDotColor
}
let hash = member.id.utf8.reduce(0) { ($0 &* 31) &+ Int($1) }
let index = abs(hash) % memberDotPalette.count
return memberDotPalette[index]
}
}
#endif