From 9066908dd9c5c83007b5c3d8b63a62bccca0c486 Mon Sep 17 00:00:00 2001 From: Krystof Date: Mon, 20 May 2024 22:41:11 +0200 Subject: [PATCH] refactor(app): map annotations --- app/metro-now.xcodeproj/project.pbxproj | 4 + .../Core/Map/MapStationAnnotationView.swift | 73 +++++++++++++++++++ app/metro-now/Core/Map/MapView.swift | 57 ++++++++------- 3 files changed, 106 insertions(+), 28 deletions(-) create mode 100644 app/metro-now/Core/Map/MapStationAnnotationView.swift diff --git a/app/metro-now.xcodeproj/project.pbxproj b/app/metro-now.xcodeproj/project.pbxproj index ba5a04b6..af5ec741 100644 --- a/app/metro-now.xcodeproj/project.pbxproj +++ b/app/metro-now.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 2D1B2C582BFAD9FB007ED5EB /* metro-routes.geojson in Resources */ = {isa = PBXBuildFile; fileRef = 2D1B2C552BFAD9FB007ED5EB /* metro-routes.geojson */; }; 2D1B2C592BFAD9FB007ED5EB /* metro-stations.geojson in Resources */ = {isa = PBXBuildFile; fileRef = 2D1B2C562BFAD9FB007ED5EB /* metro-stations.geojson */; }; 2D1B2C5A2BFAD9FB007ED5EB /* metro-stations.geojson in Resources */ = {isa = PBXBuildFile; fileRef = 2D1B2C562BFAD9FB007ED5EB /* metro-stations.geojson */; }; + 2D350E672BFBE50600F68039 /* MapStationAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D350E662BFBE50600F68039 /* MapStationAnnotationView.swift */; }; 2D4486862BFAA10A005C59CE /* metro_now_watchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4486852BFAA10A005C59CE /* metro_now_watchApp.swift */; }; 2D4486882BFAA10A005C59CE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4486872BFAA10A005C59CE /* ContentView.swift */; }; 2D44868A2BFAA10B005C59CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2D4486892BFAA10B005C59CE /* Assets.xcassets */; }; @@ -90,6 +91,7 @@ 2D1B2C512BFAD90B007ED5EB /* metroStationsTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = metroStationsTypes.swift; sourceTree = ""; }; 2D1B2C552BFAD9FB007ED5EB /* metro-routes.geojson */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "metro-routes.geojson"; path = "../../../data/metro-routes.geojson"; sourceTree = ""; }; 2D1B2C562BFAD9FB007ED5EB /* metro-stations.geojson */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "metro-stations.geojson"; path = "../../../data/metro-stations.geojson"; sourceTree = ""; }; + 2D350E662BFBE50600F68039 /* MapStationAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapStationAnnotationView.swift; sourceTree = ""; }; 2D4486832BFAA10A005C59CE /* metro-now-watch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "metro-now-watch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D4486852BFAA10A005C59CE /* metro_now_watchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = metro_now_watchApp.swift; sourceTree = ""; }; 2D4486872BFAA10A005C59CE /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -297,6 +299,7 @@ isa = PBXGroup; children = ( 2DC63A072BF4C25B00A72C7F /* MapView.swift */, + 2D350E662BFBE50600F68039 /* MapStationAnnotationView.swift */, ); path = Map; sourceTree = ""; @@ -471,6 +474,7 @@ 2D1B2C4B2BFAD807007ED5EB /* fileUtils.swift in Sources */, 2D1B2C482BFAD7F2007ED5EB /* metroUtils.swift in Sources */, 2D1B2C3B2BFAD6CC007ED5EB /* LocationModel.swift in Sources */, + 2D350E672BFBE50600F68039 /* MapStationAnnotationView.swift in Sources */, 2D84CC9F2BF8BD7000D2382B /* PlatformListModel.swift in Sources */, 2D1B2C422BFAD72C007ED5EB /* jsonUtils.swift in Sources */, 2D1B2C3F2BFAD70F007ED5EB /* timeUtils.swift in Sources */, diff --git a/app/metro-now/Core/Map/MapStationAnnotationView.swift b/app/metro-now/Core/Map/MapStationAnnotationView.swift new file mode 100644 index 00000000..dcbb0ef7 --- /dev/null +++ b/app/metro-now/Core/Map/MapStationAnnotationView.swift @@ -0,0 +1,73 @@ +// +// metro-now +// +// Created by Kryštof Krátký on 20.05.2024. +// + +import MapKit +import SwiftUI + +struct MapMetroStationView: View { + let metroLines: [String] + + var body: some View { + ZStack { + ForEach(Array(metroLines.enumerated()), id: \.0) { + index, metroLine in + let offset: CGFloat = index == 0 ? 0 : (-10 * CGFloat(index)) + + Rectangle() + .foregroundStyle(.white) + .clipShape(.rect(cornerRadius: .infinity)) + .offset(x: offset, y: offset) + + Image( + systemName: + getMetroLineIcon(metroLine) + ) + .foregroundStyle(getMetroLineColor(metroLine)) + .offset(x: offset, y: offset) + } + } + } +} + +#Preview("One station annotation") { + Map { + Annotation( + "Random place on map", coordinate: CLLocationCoordinate2D( + latitude: 50.113680, longitude: 14.449520) + ) { + MapMetroStationView( + metroLines: ["A"] + ) + } + } +} + +#Preview("Two stations annotation") { + Map { + Annotation( + "Random place on map", coordinate: CLLocationCoordinate2D( + latitude: 50.113680, longitude: 14.449520) + ) { + MapMetroStationView( + metroLines: ["A", "B"] + ) + } + } +} + +// this is not very valid for Prague, but might be useful in the future +#Preview("Multiple stations annotation") { + Map { + Annotation( + "Random place on map", coordinate: CLLocationCoordinate2D( + latitude: 50.113680, longitude: 14.449520) + ) { + MapMetroStationView( + metroLines: ["A", "B", "C", "A", "B", "C"] + ) + } + } +} diff --git a/app/metro-now/Core/Map/MapView.swift b/app/metro-now/Core/Map/MapView.swift index 5b6a4d8f..5e59290a 100644 --- a/app/metro-now/Core/Map/MapView.swift +++ b/app/metro-now/Core/Map/MapView.swift @@ -1,52 +1,53 @@ // -// MapView.swift // metro-now // import MapKit import SwiftUI +private struct MetroStationAnnotation { + let name: String + let coordinate: CLLocationCoordinate2D + let metroLines: [String] // A | B | C +} + struct MapView: View { - let metroStationsGeoJSON: MetroStationsGeoJSON! = getParsedJSONFile(.METRO_STATIONS_FILE) + @State private var metroStationAnnotations: [MetroStationAnnotation] = [] var body: some View { Map { - UserAnnotation( - ) + UserAnnotation() - ForEach(metroStationsGeoJSON!.features, id: \.properties.name) { feature in - let metroLines: [String] = Array(Set(feature.properties.platforms.map(\.name))) + ForEach(metroStationAnnotations, id: \.name) { station in + Annotation(station.name, coordinate: station.coordinate) { + MapMetroStationView(metroLines: station.metroLines) + } + } + } + .task { + let metroStationsGeoJSON: MetroStationsGeoJSON! = getParsedJSONFile(.METRO_STATIONS_FILE) + guard metroStationsGeoJSON != nil, metroStationsGeoJSON?.features != nil else { + return + } - Annotation( - feature.properties.name, + metroStationAnnotations = metroStationsGeoJSON.features.map { feature in + MetroStationAnnotation( + name: feature.properties.name, coordinate: CLLocationCoordinate2D( latitude: feature.geometry.coordinates[1], longitude: feature.geometry.coordinates[0] - ) - ) { - ZStack { - ForEach(Array(metroLines.enumerated()), id: \.0) { - index, metroLine in - - Rectangle() - .foregroundStyle(.white) - .clipShape(.rect(cornerRadius: .infinity)) - .offset(x: index == 0 ? 0 : -10, y: index == 0 ? 0 : -10) - - Image( - systemName: - getMetroLineIcon(metroLine) - ) - .foregroundStyle(getMetroLineColor(metroLine)) - .offset(x: index == 0 ? 0 : -10, y: index == 0 ? 0 : -10) - } - } - } + ), + metroLines: Array(Set(feature.properties.platforms.map(\.name))) + ) } } .mapStyle(.standard(elevation: .realistic)) .mapControls { MapUserLocationButton() + /// Shows up when you rotate the map + MapCompass() + /// 3D and 2D button on the top right + MapPitchToggle() } } }