Skip to content

Commit 3119bb6

Browse files
authored
Style update for real-time vehicle position (#407)
* Slightly rounded shape for vehicle icon on map * Style tweak for design specs * Fix double shadow
1 parent dfac5ac commit 3119bb6

File tree

2 files changed

+109
-19
lines changed

2 files changed

+109
-19
lines changed

Sources/TripKitUI/views/map annotations/TKUIVehicleAnnotationView.swift

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,33 @@ import MapKit
1313

1414
import TripKit
1515

16+
protocol TKUIVehicleAnnotation: MKAnnotation {
17+
var serviceColor: TKColor? { get }
18+
var icon: String? { get }
19+
var serviceNumber: String? { get }
20+
var bearing: NSNumber? { get }
21+
var componentsData: Data? { get }
22+
}
23+
24+
extension TKUIVehicleAnnotation {
25+
var serviceColor: TKColor? { nil }
26+
var icon: String? { nil }
27+
var serviceNumber: String? { nil }
28+
var bearing: NSNumber? { nil }
29+
var componentsData: Data? { nil }
30+
}
31+
32+
extension Vehicle: TKUIVehicleAnnotation {}
33+
1634
class TKUIVehicleAnnotationView: TKUIPulsingAnnotationView {
1735

1836
private weak var vehicleShape: TKUIVehicleView?
1937
private weak var vehicleImageView: UIImageView?
2038
private weak var label: UILabel!
2139
private weak var wrapper: UIView!
2240

23-
private let vehicleWidth = CGFloat(30)
24-
private let vehicleHeight = CGFloat(15)
41+
private let vehicleWidth = CGFloat(38)
42+
private let vehicleHeight = CGFloat(21)
2543

2644
private let disposeBag = DisposeBag()
2745

@@ -66,11 +84,11 @@ class TKUIVehicleAnnotationView: TKUIPulsingAnnotationView {
6684
}
6785

6886
guard
69-
let vehicle = annotation as? Vehicle else {
87+
let vehicle = annotation as? TKUIVehicleAnnotation else {
7088
return // happens on getting removed.
7189
}
7290

73-
calloutOffset = CGPoint(x: 0, y: 10)
91+
calloutOffset = CGPoint(x: 0, y: vehicleHeight / 2)
7492
frame = CGRect(x: 0, y: 0, width: 44, height: 44)
7593
backgroundColor = UIColor.clear
7694
isOpaque = false
@@ -95,6 +113,11 @@ class TKUIVehicleAnnotationView: TKUIPulsingAnnotationView {
95113
self.vehicleImageView = vehicleImageView
96114
} else {
97115
let vehicleShape = TKUIVehicleView(frame: vehicleRect, color: serviceColor)
116+
vehicleShape.layer.shadowColor = UIColor.black.cgColor
117+
vehicleShape.layer.shadowOpacity = 0.25
118+
vehicleShape.layer.shadowRadius = 4
119+
vehicleShape.layer.shadowOffset = CGSize(width: 0, height: 2)
120+
vehicleShape.layer.masksToBounds = false
98121
vehicleView = vehicleShape
99122
self.vehicleShape = vehicleShape
100123
}
@@ -111,7 +134,7 @@ class TKUIVehicleAnnotationView: TKUIPulsingAnnotationView {
111134
label.isOpaque = false
112135
label.textAlignment = .center
113136
label.textColor = textColorForBackgroundColor(serviceColor)
114-
label.font = TKStyleManager.customFont(forTextStyle: .caption2)
137+
label.font = TKStyleManager.semiboldCustomFont(forTextStyle: .caption2)
115138
label.adjustsFontSizeToFitWidth = true
116139
label.minimumScaleFactor = 0.75
117140
wrapper.addSubview(label)
@@ -129,12 +152,13 @@ class TKUIVehicleAnnotationView: TKUIPulsingAnnotationView {
129152
observe(vehicle)
130153
}
131154

132-
private func observe(_ vehicle: Vehicle) {
155+
private func observe(_ vehicle: TKUIVehicleAnnotation) {
156+
guard let tkVehicle = vehicle as? Vehicle else { return }
133157

134158
// Vehicle color needs to change following real-time update.
135-
vehicle.rx.observeWeakly(Data.self, "componentsData")
159+
tkVehicle.rx.observeWeakly(Data.self, "componentsData")
136160
.compactMap { data in
137-
guard let data = data else { return nil }
161+
guard let data else { return nil }
138162
let components = Vehicle.components(from: data)
139163
return TKAPI.VehicleOccupancy.average(in: components)?.0.color
140164
}
@@ -182,3 +206,23 @@ class TKUIVehicleAnnotationView: TKUIPulsingAnnotationView {
182206
return textColor
183207
}
184208
}
209+
210+
#if DEBUG
211+
class PreviewVehicle: NSObject, TKUIVehicleAnnotation {
212+
var coordinate: CLLocationCoordinate2D = .invalid
213+
var serviceNumber: String?
214+
215+
init(serviceNumber: String? = nil) {
216+
self.serviceNumber = serviceNumber
217+
}
218+
}
219+
220+
@available(iOS 17.0, *)
221+
#Preview {
222+
let wrapper = UIView(frame: .init(x: 0, y: 0, width: 300, height: 600))
223+
let vehicleView = TKUIVehicleAnnotationView(with: PreviewVehicle(serviceNumber: "311"), reuseIdentifier: "")
224+
vehicleView.frame = .init(x: 50, y: 100, width: 50, height: 50)
225+
wrapper.addSubview(vehicleView)
226+
return wrapper
227+
}
228+
#endif

Sources/TripKitUI/views/map annotations/TKUIVehicleView.swift

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,68 @@ class TKUIVehicleView: UIView {
3333

3434
override func draw(_ rect: CGRect) {
3535
guard let context = UIGraphicsGetCurrentContext() else { return }
36-
context.setStrokeColor(red: 1, green: 1, blue: 1, alpha: 1) // white
37-
context.setLineWidth(2)
38-
context.setFillColor(red: components.r, green: components.g, blue: components.b, alpha: components.a)
36+
37+
context.setLineJoin(.round)
38+
context.setLineCap(.round)
39+
40+
let rect = rect.insetBy(dx: 2, dy: 2)
41+
let radius = sqrt(CGFloat(4))
3942

43+
// Prepare path, making it a bit less pointy
4044
let midmidX = rect.maxX - rect.midY
41-
context.move(to: .init(x: rect.minX, y: rect.minY))
42-
context.addLine(to: .init(x: midmidX, y: rect.minY))
43-
context.addLine(to: .init(x: rect.maxX, y: rect.midY))
44-
context.addLine(to: .init(x: midmidX, y: rect.maxY))
45-
context.addLine(to: .init(x: rect.minX, y: rect.maxY))
46-
context.addLine(to: .init(x: rect.minX, y: rect.minY))
47-
context.closePath()
45+
let path = UIBezierPath()
46+
path.move(to: CGPoint(x: rect.minX + radius, y: rect.minY))
47+
path.addLine(to: CGPoint(x: midmidX, y: rect.minY))
48+
49+
// rounded tip
50+
path.addLine(to: CGPoint(x: rect.maxX - 2, y: rect.midY - 1))
51+
path.addArc(
52+
withCenter: CGPoint(x: rect.maxX - 4, y: rect.midY),
53+
radius: radius,
54+
startAngle: .pi * 1.75,
55+
endAngle: .pi * 0.25,
56+
clockwise: true
57+
)
58+
path.addLine(to: CGPoint(x: rect.maxX - 2, y: rect.midY + 1))
59+
60+
// bottom right-ish
61+
path.addLine(to: CGPoint(x: midmidX, y: rect.maxY))
62+
63+
// then a rounded corner at the bottom left
64+
path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.maxY))
65+
path.addArc(
66+
withCenter: CGPoint(x: rect.minX + radius, y: rect.maxY - radius),
67+
radius: radius,
68+
startAngle: .pi * 0.5,
69+
endAngle: .pi,
70+
clockwise: true
71+
)
72+
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY - radius))
4873

49-
context.drawPath(using: .fillStroke)
74+
// then a rounded corner at the top left
75+
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + radius))
76+
path.addArc(
77+
withCenter: CGPoint(x: rect.minX + radius, y: rect.minY + radius),
78+
radius: radius,
79+
startAngle: .pi,
80+
endAngle: .pi * 1.5,
81+
clockwise: true
82+
)
83+
path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.minY))
84+
85+
path.close()
86+
87+
// Fill the path
88+
context.setFillColor(UIColor(red: components.r, green: components.g, blue: components.b, alpha: components.a).cgColor)
89+
context.addPath(path.cgPath)
5090
context.fillPath()
91+
92+
// White outline on top
93+
context.setLineWidth(2)
94+
context.setStrokeColor(UIColor.white.cgColor)
95+
context.addPath(path.cgPath)
5196
context.strokePath()
5297
}
5398

5499
}
100+

0 commit comments

Comments
 (0)