← Back to index

WatchAlertHelpers

Spots Watch App
CodeWhat It DoesHow It Does It
▶ IMPORTS
import SwiftUIFramework importsImports SwiftUI.
▶ LINE COLOURS (MTA PALETTE) — CANONICAL DEFINITION FOR WATCH TARGET
func watchLineColor(for line: String) -> Color { switch line { case "1", "2", "3": return Color(red: 238/255, green: 53/255, blue: 46/255) case "4", "5", "6": return Color(red: 0/255, green: 147/255, blue: 60/255) case "7": return Color(red: 185/255, green: 51/255, blue: 173/255) case "A", "C", "E": return Color(red: 0/255, green: 57/255, blue: 166/255) case "B", "D", "F", "M": return Color(red: 255/255, green: 99/255, blue: 25/255) case "G": return Color(red: 108/255, green: 190/255, blue: 69/255) case "J", "Z": return Color(red: 153/255, green: 102/255, blue: 51/255) case "L": return Color(red: 167/255, green: 169/255, blue: 172/255) case "N", "Q", "R", "W": return Color(red: 252/255, green: 204/255, blue: 10/255) default: return .gray } }`watchLineColor()` functionImplements `watchLineColor`. Returns `Color`.
func watchLineTextColor(for line: String) -> Color { ["N", "Q", "R", "W"].contains(line) ? .black : .white }`watchLineTextColor()` functionImplements `watchLineTextColor`. Returns `Color`.
▶ INLINE BADGE IMAGE (WATCHOS VERSION USING IMAGERENDERER)
/// Renders a subway line badge as a UIImage so it can be embedded inline in a SwiftUI Text. /// Uses SwiftUI's ImageRenderer — the watchOS equivalent of UIGraphicsImageRenderer. @MainActor private func watchLineBadgeImage(for line: String, size: CGFloat = 14) -> Image? { let badge = ZStack { Circle() .fill(watchLineColor(for: line)) .frame(width: size, height: size) Text(line) .font(.system(size: size * 0.62, weight: .bold)) .foregroundColor(watchLineTextColor(for: line)) } .frame(width: size, height: size) let renderer = ImageRenderer(content: badge) renderer.scale = 2.0 if let cgImage = renderer.cgImage { return Image(cgImage, scale: 2.0, label: Text(line)) } return nil }Documentation commentDescribes the following declaration.
▶ INLINE ALERT-HEADER TEXT (SAME TECHNIQUE AS NEARBYMAPVIEW'S ALERTHEADERTEXT)
/// Parses `[X]` tokens in an alert header and returns a concatenated SwiftUI Text /// where each token is replaced by a coloured circle badge — identical to the /// approach used in NearbyMapView's black station-callout bubbles. @MainActor func watchAlertHeaderText(_ header: String, fontSize: CGFloat = 13, textColor: Color = .primary) -> Text { let plain = Text(header) .font(.system(size: fontSize)) .foregroundColor(textColor)Documentation commentDescribes the following declaration.
guard let regex = try? NSRegularExpression(pattern: #"\[([A-Z0-9]{1,2})\]"#) else { return plain }`regex` letProperty `regex`. Type: `#"\[([A-Z0-9]{1,2})\]"#)`.
let ns = header as NSString let matches = regex.matches(in: header, range: NSRange(location: 0, length: ns.length)) guard !matches.isEmpty else { return plain }`ns` letProperty `ns`.
let style = Font.system(size: fontSize) var result = Text("") var lastEnd = 0`style` letProperty `style`. Type: `fontSize)`.
for match in matches { let matchRange = match.range if matchRange.location > lastEnd { let seg = ns.substring(with: NSRange(location: lastEnd, length: matchRange.location - lastEnd)) result = Text("\(result)\(Text(seg).font(style).foregroundColor(textColor))") } if let lineRange = Range(match.range(at: 1), in: header) { let line = String(header[lineRange]) if let img = watchLineBadgeImage(for: line, size: fontSize + 2) { result = Text("\(result)\(Text(img).baselineOffset(-1))") } else { // Fallback: plain text label result = Text("\(result)\(Text("[\(line)]").font(style).foregroundColor(textColor))") } } lastEnd = matchRange.location + matchRange.length }Code blockSee source code for full implementation.
if lastEnd < ns.length { result = Text("\(result)\(Text(ns.substring(from: lastEnd)).font(style).foregroundColor(textColor))") }Code blockSee source code for full implementation.
return result }Code blockSee source code for full implementation.
▶ [X] TOKEN PARSER (USED TO EXTRACT AFFECTED ROUTES WHEN HEADER HAS NO TOKENS)
/// Splits an MTA alert header into clean text + extracted line tokens like [A], [C], [1]. func parseAlertHeader(_ text: String) -> (text: String, lines: [String]) { guard let re = try? NSRegularExpression(pattern: "\\[([A-Z0-9]+)\\]") else { return (text, []) } let ns = text as NSString let range = NSRange(location: 0, length: ns.length) var extracted: [String] = [] for m in re.matches(in: text, range: range) { if m.numberOfRanges > 1 { extracted.append(ns.substring(with: m.range(at: 1))) } } var cleaned = re.stringByReplacingMatches(in: text, range: range, withTemplate: "") while cleaned.contains(" ") { cleaned = cleaned.replacingOccurrences(of: " ", with: " ") } cleaned = cleaned.trimmingCharacters(in: .whitespaces) return (cleaned, extracted) }Documentation commentDescribes the following declaration.
▶ LINE BADGE (SMALL COLOURED CIRCLE, USED IN WATCHSCHEDULEVIEW)
struct AlertLineBadge: View { let line: String var size: CGFloat = 20 var body: some View { ZStack { Circle() .fill(watchLineColor(for: line)) .frame(width: size, height: size) Text(line) .font(.system(size: size * 0.5, weight: .bold)) .foregroundColor(watchLineTextColor(for: line)) } } }`AlertLineBadge` structDefines the `AlertLineBadge` struct. Conforms to View.
▶ ALERT ROW
struct WatchAlertRow: View { let alert: WatchSubwayAlert var body: some View { let headerHasTokens = alert.header.contains("[") VStack(alignment: .leading, spacing: 5) { // If the header has no [X] tokens, show affectedRoutes as coloured badges if !headerHasTokens && !alert.affectedRoutes.isEmpty { HStack(spacing: 4) { ForEach(Array(alert.affectedRoutes.prefix(8).enumerated()), id: \.offset) { _, line in AlertLineBadge(line: line, size: 18) } Spacer(minLength: 0) } } // Alert type if !alert.alertType.isEmpty { Text(alert.alertType) .font(.system(size: 11, weight: .semibold)) .foregroundColor(.orange) } // Header — [X] tokens replaced inline with coloured circle badges, // exactly as NearbyMapView does in its station callout bubbles. watchAlertHeaderText(alert.header, fontSize: 13, textColor: .primary) .fixedSize(horizontal: false, vertical: true) } .padding(.vertical, 5) } }`WatchAlertRow` structDefines the `WatchAlertRow` struct. Conforms to View.