jsdw_ios/QuickLocation/Section/Map/MemberAnnotationView.swift

293 lines
9.5 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
// image bounds 1px SDK bounds
image = Self.transparentPixel
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()
}
override func prepareForReuse() {
super.prepareForReuse()
showsNameTag = false
nameTagView.isHidden = true
nameLabel.text = nil
onTap = nil
layoutMarker()
}
/// didAdd bounds/centerOffset layout
func applyAnchor() {
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
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 let transparentPixel: UIImage = {
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0)
UIColor.clear.setFill()
UIRectFill(CGRect(x: 0, y: 0, width: 1, height: 1))
let img = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
UIGraphicsEndImageContext()
return img
}()
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