Compare commits
	
		
			10 Commits
		
	
	
		
			c759d5e3b3
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f2aa710a5e | ||
|   | a64ef8d469 | ||
|   | bf7175bdd6 | ||
|   | 171e181387 | ||
|   | 8ec74c1012 | ||
|   | 546bdc4b2b | ||
|   | b72ca2dd3d | ||
| 17cc4190b8 | |||
| c97a50cd4b | |||
| e0d7c72117 | 
| @@ -6,8 +6,33 @@ | |||||||
| 	objectVersion = 77; | 	objectVersion = 77; | ||||||
| 	objects = { | 	objects = { | ||||||
|  |  | ||||||
|  | /* Begin PBXCopyFilesBuildPhase section */ | ||||||
|  | 		6C6E6C442D00962A003D3BA9 /* Embed Foundation Extensions */ = { | ||||||
|  | 			isa = PBXCopyFilesBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			dstPath = ""; | ||||||
|  | 			dstSubfolderSpec = 13; | ||||||
|  | 			files = ( | ||||||
|  | 			); | ||||||
|  | 			name = "Embed Foundation Extensions"; | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 		}; | ||||||
|  | 		6C6E6C5C2D00A2EA003D3BA9 /* Embed Frameworks */ = { | ||||||
|  | 			isa = PBXCopyFilesBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			dstPath = ""; | ||||||
|  | 			dstSubfolderSpec = 10; | ||||||
|  | 			files = ( | ||||||
|  | 			); | ||||||
|  | 			name = "Embed Frameworks"; | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 		}; | ||||||
|  | /* End PBXCopyFilesBuildPhase section */ | ||||||
|  |  | ||||||
| /* Begin PBXFileReference section */ | /* Begin PBXFileReference section */ | ||||||
| 		6C5D06452CF209960006CDE9 /* StepMap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StepMap.app; sourceTree = BUILT_PRODUCTS_DIR; }; | 		6C5D06452CF209960006CDE9 /* StepMap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StepMap.app; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||||
|  | 		6C6E6C352D009628003D3BA9 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; | ||||||
|  | 		6C6E6C372D009628003D3BA9 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; | ||||||
| /* End PBXFileReference section */ | /* End PBXFileReference section */ | ||||||
|  |  | ||||||
| /* Begin PBXFileSystemSynchronizedRootGroup section */ | /* Begin PBXFileSystemSynchronizedRootGroup section */ | ||||||
| @@ -33,6 +58,7 @@ | |||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				6C5D06472CF209960006CDE9 /* StepMap */, | 				6C5D06472CF209960006CDE9 /* StepMap */, | ||||||
|  | 				6C6E6C342D009628003D3BA9 /* Frameworks */, | ||||||
| 				6C5D06462CF209960006CDE9 /* Products */, | 				6C5D06462CF209960006CDE9 /* Products */, | ||||||
| 			); | 			); | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -45,6 +71,15 @@ | |||||||
| 			name = Products; | 			name = Products; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		6C6E6C342D009628003D3BA9 /* Frameworks */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				6C6E6C352D009628003D3BA9 /* WidgetKit.framework */, | ||||||
|  | 				6C6E6C372D009628003D3BA9 /* SwiftUI.framework */, | ||||||
|  | 			); | ||||||
|  | 			name = Frameworks; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| /* End PBXGroup section */ | /* End PBXGroup section */ | ||||||
|  |  | ||||||
| /* Begin PBXNativeTarget section */ | /* Begin PBXNativeTarget section */ | ||||||
| @@ -55,6 +90,8 @@ | |||||||
| 				6C5D06412CF209960006CDE9 /* Sources */, | 				6C5D06412CF209960006CDE9 /* Sources */, | ||||||
| 				6C5D06422CF209960006CDE9 /* Frameworks */, | 				6C5D06422CF209960006CDE9 /* Frameworks */, | ||||||
| 				6C5D06432CF209960006CDE9 /* Resources */, | 				6C5D06432CF209960006CDE9 /* Resources */, | ||||||
|  | 				6C6E6C442D00962A003D3BA9 /* Embed Foundation Extensions */, | ||||||
|  | 				6C6E6C5C2D00A2EA003D3BA9 /* Embed Frameworks */, | ||||||
| 			); | 			); | ||||||
| 			buildRules = ( | 			buildRules = ( | ||||||
| 			); | 			); | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								StepMap.xcodeproj/project.xcworkspace/xcuserdata/oliverhnat.xcuserdatad/UserInterfaceState.xcuserstate
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								StepMap.xcodeproj/project.xcworkspace/xcuserdata/oliverhnat.xcuserdatad/UserInterfaceState.xcuserstate
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										78
									
								
								StepMap.xcodeproj/xcshareddata/xcschemes/StepMap.xcscheme
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								StepMap.xcodeproj/xcshareddata/xcschemes/StepMap.xcscheme
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <Scheme | ||||||
|  |    LastUpgradeVersion = "1630" | ||||||
|  |    version = "1.7"> | ||||||
|  |    <BuildAction | ||||||
|  |       parallelizeBuildables = "YES" | ||||||
|  |       buildImplicitDependencies = "YES" | ||||||
|  |       buildArchitectures = "Automatic"> | ||||||
|  |       <BuildActionEntries> | ||||||
|  |          <BuildActionEntry | ||||||
|  |             buildForTesting = "YES" | ||||||
|  |             buildForRunning = "YES" | ||||||
|  |             buildForProfiling = "YES" | ||||||
|  |             buildForArchiving = "YES" | ||||||
|  |             buildForAnalyzing = "YES"> | ||||||
|  |             <BuildableReference | ||||||
|  |                BuildableIdentifier = "primary" | ||||||
|  |                BlueprintIdentifier = "6C5D06442CF209960006CDE9" | ||||||
|  |                BuildableName = "StepMap.app" | ||||||
|  |                BlueprintName = "StepMap" | ||||||
|  |                ReferencedContainer = "container:StepMap.xcodeproj"> | ||||||
|  |             </BuildableReference> | ||||||
|  |          </BuildActionEntry> | ||||||
|  |       </BuildActionEntries> | ||||||
|  |    </BuildAction> | ||||||
|  |    <TestAction | ||||||
|  |       buildConfiguration = "Debug" | ||||||
|  |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|  |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|  |       shouldUseLaunchSchemeArgsEnv = "YES" | ||||||
|  |       shouldAutocreateTestPlan = "YES"> | ||||||
|  |    </TestAction> | ||||||
|  |    <LaunchAction | ||||||
|  |       buildConfiguration = "Debug" | ||||||
|  |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|  |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|  |       launchStyle = "0" | ||||||
|  |       useCustomWorkingDirectory = "NO" | ||||||
|  |       ignoresPersistentStateOnLaunch = "NO" | ||||||
|  |       debugDocumentVersioning = "YES" | ||||||
|  |       debugServiceExtension = "internal" | ||||||
|  |       allowLocationSimulation = "YES"> | ||||||
|  |       <BuildableProductRunnable | ||||||
|  |          runnableDebuggingMode = "0"> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "6C5D06442CF209960006CDE9" | ||||||
|  |             BuildableName = "StepMap.app" | ||||||
|  |             BlueprintName = "StepMap" | ||||||
|  |             ReferencedContainer = "container:StepMap.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </BuildableProductRunnable> | ||||||
|  |    </LaunchAction> | ||||||
|  |    <ProfileAction | ||||||
|  |       buildConfiguration = "Release" | ||||||
|  |       shouldUseLaunchSchemeArgsEnv = "YES" | ||||||
|  |       savedToolIdentifier = "" | ||||||
|  |       useCustomWorkingDirectory = "NO" | ||||||
|  |       debugDocumentVersioning = "YES"> | ||||||
|  |       <BuildableProductRunnable | ||||||
|  |          runnableDebuggingMode = "0"> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "6C5D06442CF209960006CDE9" | ||||||
|  |             BuildableName = "StepMap.app" | ||||||
|  |             BlueprintName = "StepMap" | ||||||
|  |             ReferencedContainer = "container:StepMap.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </BuildableProductRunnable> | ||||||
|  |    </ProfileAction> | ||||||
|  |    <AnalyzeAction | ||||||
|  |       buildConfiguration = "Debug"> | ||||||
|  |    </AnalyzeAction> | ||||||
|  |    <ArchiveAction | ||||||
|  |       buildConfiguration = "Release" | ||||||
|  |       revealArchiveInOrganizer = "YES"> | ||||||
|  |    </ArchiveAction> | ||||||
|  | </Scheme> | ||||||
| @@ -0,0 +1,124 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <Scheme | ||||||
|  |    LastUpgradeVersion = "1630" | ||||||
|  |    wasCreatedForAppExtension = "YES" | ||||||
|  |    version = "2.0"> | ||||||
|  |    <BuildAction | ||||||
|  |       parallelizeBuildables = "YES" | ||||||
|  |       buildImplicitDependencies = "YES" | ||||||
|  |       buildArchitectures = "Automatic"> | ||||||
|  |       <BuildActionEntries> | ||||||
|  |          <BuildActionEntry | ||||||
|  |             buildForTesting = "YES" | ||||||
|  |             buildForRunning = "YES" | ||||||
|  |             buildForProfiling = "YES" | ||||||
|  |             buildForArchiving = "YES" | ||||||
|  |             buildForAnalyzing = "YES"> | ||||||
|  |             <BuildableReference | ||||||
|  |                BuildableIdentifier = "primary" | ||||||
|  |                BlueprintIdentifier = "6C6E6C322D009628003D3BA9" | ||||||
|  |                BuildableName = "StepMapWidgetsExtension.appex" | ||||||
|  |                BlueprintName = "StepMapWidgetsExtension" | ||||||
|  |                ReferencedContainer = "container:StepMap.xcodeproj"> | ||||||
|  |             </BuildableReference> | ||||||
|  |          </BuildActionEntry> | ||||||
|  |          <BuildActionEntry | ||||||
|  |             buildForTesting = "YES" | ||||||
|  |             buildForRunning = "YES" | ||||||
|  |             buildForProfiling = "YES" | ||||||
|  |             buildForArchiving = "YES" | ||||||
|  |             buildForAnalyzing = "YES"> | ||||||
|  |             <BuildableReference | ||||||
|  |                BuildableIdentifier = "primary" | ||||||
|  |                BlueprintIdentifier = "6C5D06442CF209960006CDE9" | ||||||
|  |                BuildableName = "StepMap.app" | ||||||
|  |                BlueprintName = "StepMap" | ||||||
|  |                ReferencedContainer = "container:StepMap.xcodeproj"> | ||||||
|  |             </BuildableReference> | ||||||
|  |          </BuildActionEntry> | ||||||
|  |       </BuildActionEntries> | ||||||
|  |    </BuildAction> | ||||||
|  |    <TestAction | ||||||
|  |       buildConfiguration = "Debug" | ||||||
|  |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|  |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|  |       shouldUseLaunchSchemeArgsEnv = "YES" | ||||||
|  |       shouldAutocreateTestPlan = "YES"> | ||||||
|  |    </TestAction> | ||||||
|  |    <LaunchAction | ||||||
|  |       buildConfiguration = "Debug" | ||||||
|  |       selectedDebuggerIdentifier = "" | ||||||
|  |       selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn" | ||||||
|  |       launchStyle = "0" | ||||||
|  |       askForAppToLaunch = "Yes" | ||||||
|  |       useCustomWorkingDirectory = "NO" | ||||||
|  |       ignoresPersistentStateOnLaunch = "NO" | ||||||
|  |       debugDocumentVersioning = "YES" | ||||||
|  |       debugServiceExtension = "internal" | ||||||
|  |       allowLocationSimulation = "YES" | ||||||
|  |       launchAutomaticallySubstyle = "2"> | ||||||
|  |       <RemoteRunnable | ||||||
|  |          runnableDebuggingMode = "2" | ||||||
|  |          BundleIdentifier = "com.apple.springboard"> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "6C6E6C322D009628003D3BA9" | ||||||
|  |             BuildableName = "StepMapWidgetsExtension.appex" | ||||||
|  |             BlueprintName = "StepMapWidgetsExtension" | ||||||
|  |             ReferencedContainer = "container:StepMap.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </RemoteRunnable> | ||||||
|  |       <MacroExpansion> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "6C5D06442CF209960006CDE9" | ||||||
|  |             BuildableName = "StepMap.app" | ||||||
|  |             BlueprintName = "StepMap" | ||||||
|  |             ReferencedContainer = "container:StepMap.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </MacroExpansion> | ||||||
|  |       <EnvironmentVariables> | ||||||
|  |          <EnvironmentVariable | ||||||
|  |             key = "_XCWidgetKind" | ||||||
|  |             value = "" | ||||||
|  |             isEnabled = "YES"> | ||||||
|  |          </EnvironmentVariable> | ||||||
|  |          <EnvironmentVariable | ||||||
|  |             key = "_XCWidgetDefaultView" | ||||||
|  |             value = "timeline" | ||||||
|  |             isEnabled = "YES"> | ||||||
|  |          </EnvironmentVariable> | ||||||
|  |          <EnvironmentVariable | ||||||
|  |             key = "_XCWidgetFamily" | ||||||
|  |             value = "systemMedium" | ||||||
|  |             isEnabled = "YES"> | ||||||
|  |          </EnvironmentVariable> | ||||||
|  |       </EnvironmentVariables> | ||||||
|  |    </LaunchAction> | ||||||
|  |    <ProfileAction | ||||||
|  |       buildConfiguration = "Release" | ||||||
|  |       shouldUseLaunchSchemeArgsEnv = "YES" | ||||||
|  |       savedToolIdentifier = "" | ||||||
|  |       useCustomWorkingDirectory = "NO" | ||||||
|  |       debugDocumentVersioning = "YES" | ||||||
|  |       askForAppToLaunch = "Yes" | ||||||
|  |       launchAutomaticallySubstyle = "2"> | ||||||
|  |       <BuildableProductRunnable | ||||||
|  |          runnableDebuggingMode = "0"> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "6C5D06442CF209960006CDE9" | ||||||
|  |             BuildableName = "StepMap.app" | ||||||
|  |             BlueprintName = "StepMap" | ||||||
|  |             ReferencedContainer = "container:StepMap.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </BuildableProductRunnable> | ||||||
|  |    </ProfileAction> | ||||||
|  |    <AnalyzeAction | ||||||
|  |       buildConfiguration = "Debug"> | ||||||
|  |    </AnalyzeAction> | ||||||
|  |    <ArchiveAction | ||||||
|  |       buildConfiguration = "Release" | ||||||
|  |       revealArchiveInOrganizer = "YES"> | ||||||
|  |    </ArchiveAction> | ||||||
|  | </Scheme> | ||||||
| @@ -5,10 +5,33 @@ | |||||||
| 	<key>SchemeUserState</key> | 	<key>SchemeUserState</key> | ||||||
| 	<dict> | 	<dict> | ||||||
| 		<key>StepMap.xcscheme_^#shared#^_</key> | 		<key>StepMap.xcscheme_^#shared#^_</key> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>orderHint</key> | ||||||
|  | 			<integer>1</integer> | ||||||
|  | 		</dict> | ||||||
|  | 		<key>StepMapUtils.xcscheme_^#shared#^_</key> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>orderHint</key> | ||||||
|  | 			<integer>2</integer> | ||||||
|  | 		</dict> | ||||||
|  | 		<key>StepMapWidgetsExtension.xcscheme_^#shared#^_</key> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>orderHint</key> | 			<key>orderHint</key> | ||||||
| 			<integer>0</integer> | 			<integer>0</integer> | ||||||
| 		</dict> | 		</dict> | ||||||
| 	</dict> | 	</dict> | ||||||
|  | 	<key>SuppressBuildableAutocreation</key> | ||||||
|  | 	<dict> | ||||||
|  | 		<key>6C5D06442CF209960006CDE9</key> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>primary</key> | ||||||
|  | 			<true/> | ||||||
|  | 		</dict> | ||||||
|  | 		<key>6C6E6C322D009628003D3BA9</key> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>primary</key> | ||||||
|  | 			<true/> | ||||||
|  | 		</dict> | ||||||
|  | 	</dict> | ||||||
| </dict> | </dict> | ||||||
| </plist> | </plist> | ||||||
|   | |||||||
							
								
								
									
										56
									
								
								StepMap/AnnotationView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								StepMap/AnnotationView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | // | ||||||
|  | //  AnnotationView.swift | ||||||
|  | //  StepMap | ||||||
|  | // | ||||||
|  | //  Created by Oliver Hnat on 16/05/2025. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import SwiftUI | ||||||
|  | import MapKit | ||||||
|  |  | ||||||
|  | struct AnnotationView: View { | ||||||
|  |     var pm: CLPlacemark | ||||||
|  |     var title: String? | ||||||
|  |     var coordinate: CLLocation | ||||||
|  |     @ObservedObject var viewModel: ViewModel | ||||||
|  |     var body: some View { | ||||||
|  |         ZStack { | ||||||
|  |             Rectangle() | ||||||
|  |                 .fill(.thinMaterial) | ||||||
|  |                 .ignoresSafeArea() | ||||||
|  |             VStack(alignment: .leading) { | ||||||
|  |                 HStack(alignment: .top) { | ||||||
|  |                     Text((title ?? pm.areasOfInterest?.first ?? pm.name) ?? "\(coordinate.coordinate.latitude.description)º, \(coordinate.coordinate.longitude.description)") | ||||||
|  |                         .font(.title) | ||||||
|  |                         .bold() | ||||||
|  |                     Spacer() | ||||||
|  |                     Button(action: { | ||||||
|  |                         viewModel.showDetails = false | ||||||
|  |                          | ||||||
|  |                     }, label: { | ||||||
|  |                         Image(systemName: "multiply.circle") | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 .padding(.horizontal) | ||||||
|  |                 .padding(.top, 20) | ||||||
|  |                 Spacer() | ||||||
|  |                 Text(pm.locality ?? "") | ||||||
|  |                 Text(pm.name ?? "") | ||||||
|  |                 Text("Name: \(pm.name ?? "")") | ||||||
|  |                 Text("Street: \(pm.thoroughfare ?? "")") | ||||||
|  |                 Text("City: \(pm.locality ?? "")") | ||||||
|  |                 Text("Postal Code: \(pm.postalCode ?? "")") | ||||||
|  |                 Text("Country: \(pm.country ?? "")") | ||||||
|  |                 ForEach(pm.areasOfInterest ?? [], id: \.self) { area in | ||||||
|  |                     Text("Area: \(area)") | ||||||
|  |                 } | ||||||
|  |                 if let coordinate = pm.location?.coordinate { | ||||||
|  |                     let mkPlacemark = MKPlacemark(coordinate: coordinate) | ||||||
|  |                     Image(systemName: Defaults.getIconFor(pointOfInterest: MKMapItem(placemark: mkPlacemark).pointOfInterestCategory)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .frame(maxWidth: .infinity) | ||||||
|  |             .ignoresSafeArea() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -14,16 +14,8 @@ struct ContentView: View { | |||||||
|     @ObservedObject var viewModel = ViewModel() |     @ObservedObject var viewModel = ViewModel() | ||||||
|     @StateObject var locationManager = LocationManager() |     @StateObject var locationManager = LocationManager() | ||||||
|     @StateObject var healthKitManager = HealthKitManager() |     @StateObject var healthKitManager = HealthKitManager() | ||||||
|  |  | ||||||
|     @State private var position = MapCameraPosition.automatic |  | ||||||
|     @State private var showSearch: Bool = true |  | ||||||
|     @State private var directions: [MKRoute] = [] |  | ||||||
|     @State private var destination: MKMapItem? |  | ||||||
|  |  | ||||||
|      |      | ||||||
|     @State var healthKitAccess = false |      | ||||||
|     @State var stepLength: Double? |  | ||||||
|  |  | ||||||
|     // TODO: create a map |     // TODO: create a map | ||||||
|     // Add navigation to the map |     // Add navigation to the map | ||||||
|     // after you click the navigation button, show the start and end place on the map with a tag or whatever it's called |     // after you click the navigation button, show the start and end place on the map with a tag or whatever it's called | ||||||
| @@ -36,57 +28,21 @@ struct ContentView: View { | |||||||
|     // Add favorite locations - like home, work, etc (probably should be stored in core data tho:/) |     // Add favorite locations - like home, work, etc (probably should be stored in core data tho:/) | ||||||
|      |      | ||||||
|     // FIX: search is bad lol |     // FIX: search is bad lol | ||||||
|  |      | ||||||
|      |      | ||||||
|     // How to speed up? |     // How to speed up? | ||||||
|     // calculate only the distance between the start and end instead of getting directions for everything |     // calculate only the distance between the start and end instead of getting directions for everything | ||||||
|     // if user clicks on the place, display better view and then calculate route there |     // if user clicks on the place, display better view and then calculate route there | ||||||
|     var body: some View { |     var body: some View { | ||||||
|         Map(position: $position) { |         MapView(locationManager: locationManager, viewModel: viewModel) | ||||||
|             ForEach(0..<directions.count) { i in |             .ignoresSafeArea() | ||||||
|                 if destination != nil { |             .onAppear { | ||||||
|                     Marker(item: destination!) |                 Task { | ||||||
|  |                     await healthKitManager.requestAccess() | ||||||
|  |                     viewModel.stepLength = await healthKitManager.getStepLength() | ||||||
|                 } |                 } | ||||||
|                 MapPolyline(directions[i].polyline) |  | ||||||
|                     .stroke(Defaults.routeColor[i], lineWidth: Defaults.routeWidth) |  | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|         .sheet( |  | ||||||
|             isPresented: $showSearch, |  | ||||||
|             content: { |  | ||||||
|                 SearchView( |  | ||||||
|                     directions: $directions, stepLength: $stepLength, |  | ||||||
|                     locationManager: locationManager, destination: $destination |  | ||||||
|                 ) |  | ||||||
|                 .ignoresSafeArea() |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|         .mapControls { |  | ||||||
|             // TODO: make sure the user location stays on the map even if camera moves |  | ||||||
|             MapUserLocationButton() |  | ||||||
|                 .onTapGesture { |  | ||||||
|                     locationManager.requestAuthorization() |  | ||||||
|                     locationManager.requestLocation() |  | ||||||
|                     if let userLocation = locationManager.location { |  | ||||||
|                         position = .camera( |  | ||||||
|                             MapCamera(centerCoordinate: userLocation, distance: 1000)) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             MapCompass() |  | ||||||
|         } |  | ||||||
|         .onAppear { |  | ||||||
|             locationManager.requestAuthorization() |  | ||||||
|             locationManager.requestLocation() |  | ||||||
|             if let userLocation = locationManager.location { |  | ||||||
|                 position = .camera(MapCamera(centerCoordinate: userLocation, distance: 1000)) |  | ||||||
|             } |  | ||||||
|             Task { |  | ||||||
|                 await healthKitManager.requestAccess() |  | ||||||
|                 stepLength = await healthKitManager.getStepLength() |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|      |  | ||||||
|     func getLastPointFor(route: MKRoute) -> CLLocationCoordinate2D? { |     func getLastPointFor(route: MKRoute) -> CLLocationCoordinate2D? { | ||||||
|         let pointCount =  route.polyline.pointCount |         let pointCount =  route.polyline.pointCount | ||||||
|         if pointCount > 0 { |         if pointCount > 0 { | ||||||
| @@ -94,7 +50,7 @@ struct ContentView: View { | |||||||
|         } |         } | ||||||
|         return nil |         return nil | ||||||
|     } |     } | ||||||
|  |      | ||||||
|     func save(value: String) { |     func save(value: String) { | ||||||
|         viewModel.saveValue(value) |         viewModel.saveValue(value) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -13,9 +13,12 @@ class HealthKitManager: ObservableObject { | |||||||
|     let allTypes: Set = [ |     let allTypes: Set = [ | ||||||
|         HKQuantityType(.walkingSpeed), |         HKQuantityType(.walkingSpeed), | ||||||
|         HKQuantityType(.walkingStepLength), |         HKQuantityType(.walkingStepLength), | ||||||
|  |         HKQuantityType(.stepCount) | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|     var stepLength: Double? |     var stepLength: Double? | ||||||
|  |      | ||||||
|  |     var stepCount: Int? | ||||||
| 
 | 
 | ||||||
|     func requestAccess() async { |     func requestAccess() async { | ||||||
|         do { |         do { | ||||||
| @@ -49,5 +52,23 @@ class HealthKitManager: ObservableObject { | |||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  | //    func getCurrentStepCount() async -> Double? { | ||||||
|  | //        let stepCountType = HKQuantityType(.stepCount) | ||||||
|  | // | ||||||
|  | //        let query = HKStatisticsQueryDescriptor( | ||||||
|  | //            predicate: HKSamplePredicate.quantitySample(type: stepCountType), | ||||||
|  | //            options: .cumulativeSum) | ||||||
|  | //         | ||||||
|  | //        do { | ||||||
|  | //            let results = try await query.result(for: healthStore) | ||||||
|  | //            stepCount = results?.sumQuantity().val | ||||||
|  | //            return stepCount | ||||||
|  | //        } catch { | ||||||
|  | //            fatalError( | ||||||
|  | //                "Something went wrong while getting step length from healthKit: \(error.localizedDescription)" | ||||||
|  | //            ) | ||||||
|  | //        } | ||||||
|  | //    } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @@ -8,7 +8,7 @@ | |||||||
| import CoreLocation | import CoreLocation | ||||||
| import Foundation | import Foundation | ||||||
| 
 | 
 | ||||||
| class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { | public class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { | ||||||
|     let manager = CLLocationManager() |     let manager = CLLocationManager() | ||||||
| 
 | 
 | ||||||
|     @Published var location: CLLocationCoordinate2D? |     @Published var location: CLLocationCoordinate2D? | ||||||
| @@ -26,13 +26,13 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { | |||||||
|         manager.requestWhenInUseAuthorization() |         manager.requestWhenInUseAuthorization() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { |     public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { | ||||||
|         location = locations.first?.coordinate |         location = locations.first?.coordinate | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| extension LocationManager { | extension LocationManager { | ||||||
|     func locationManager( |     public func locationManager( | ||||||
|         _ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus |         _ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus | ||||||
|     ) { |     ) { | ||||||
|         switch status { |         switch status { | ||||||
| @@ -51,7 +51,16 @@ extension LocationManager { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) { |     public func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) { | ||||||
|         print(error) |         print(error) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | extension CLLocationCoordinate2D: Equatable { | ||||||
|  |     public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool { | ||||||
|  |         return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -13,20 +13,20 @@ import SwiftUI | |||||||
| struct SearchItemView: View { | struct SearchItemView: View { | ||||||
|     var location: MKMapItem |     var location: MKMapItem | ||||||
|     @State var distance: CLLocationDistance? |     @State var distance: CLLocationDistance? | ||||||
|     @Binding var directions: [MKRoute] |  | ||||||
|     @Binding var stepLength: Double? |  | ||||||
|     @Binding var showSteps: Bool |     @Binding var showSteps: Bool | ||||||
|     @State var localDirections: [MKRoute] = [] |     @State var localDirections: [MKRoute] = [] | ||||||
|     @Binding var destination: MKMapItem? |     @ObservedObject var viewModel: ViewModel | ||||||
|  |  | ||||||
|     var body: some View { |     var body: some View { | ||||||
|  |  | ||||||
|         Button( |         Button( | ||||||
|             action: { |             action: { | ||||||
|                 if localDirections == [] { |                 if localDirections == [] { | ||||||
|  |                     print("finding directions") | ||||||
|                     findDirections() |                     findDirections() | ||||||
|                 } |                 } | ||||||
|                 directions = localDirections |                 viewModel.directions = localDirections | ||||||
|  |                 print("Directions set") | ||||||
|             }, |             }, | ||||||
|             label: { |             label: { | ||||||
|                 HStack { |                 HStack { | ||||||
| @@ -86,7 +86,7 @@ struct SearchItemView: View { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     func formatDistance(distance: CLLocationDistance) -> String { |     func formatDistance(distance: CLLocationDistance) -> String { | ||||||
|         let steps = distance / (stepLength ?? 1) |         let steps = distance / (viewModel.stepLength ?? 1) | ||||||
|         if steps != 0 && showSteps { |         if steps != 0 && showSteps { | ||||||
|             let formatter = NumberFormatter() |             let formatter = NumberFormatter() | ||||||
|             formatter.maximumFractionDigits = 0 |             formatter.maximumFractionDigits = 0 | ||||||
| @@ -118,7 +118,7 @@ struct SearchItemView: View { | |||||||
|             } |             } | ||||||
|             self.localDirections = response.routes |             self.localDirections = response.routes | ||||||
|             self.distance = response.routes.first?.distance |             self.distance = response.routes.first?.distance | ||||||
|             self.destination = location |             viewModel.destination = location | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,56 +11,56 @@ import SwiftUI | |||||||
| struct SearchView: View { | struct SearchView: View { | ||||||
|     @State private var query: String = "" |     @State private var query: String = "" | ||||||
|     @State private var locations: [MKMapItem] = [] |     @State private var locations: [MKMapItem] = [] | ||||||
|     @Binding var directions: [MKRoute] |  | ||||||
|     @Binding var stepLength: Double? |  | ||||||
|     @State var showSteps = true |     @State var showSteps = true | ||||||
|     var locationManager: LocationManager |     var locationManager: LocationManager | ||||||
|     @Binding var destination: MKMapItem? |     @ObservedObject var viewModel: ViewModel | ||||||
|  |  | ||||||
|     var body: some View { |     var body: some View { | ||||||
|         VStack { |         ZStack { | ||||||
|             HStack { |             Rectangle() | ||||||
|                 Image(systemName: "magnifyingglass") |                 .fill(.thinMaterial) | ||||||
|                 TextField("Search for any location", text: $query) |                 .ignoresSafeArea() | ||||||
|                     .autocorrectionDisabled() |             VStack { | ||||||
|                     .onChange(of: self.query) { |                 HStack { | ||||||
|                         if query.count > 0 { |                     Image(systemName: "magnifyingglass") | ||||||
|                             search(for: self.query) |                     TextField("Search for any location", text: $query) | ||||||
|                         } else { |                         .autocorrectionDisabled() | ||||||
|                             self.locations = [] |                         .onChange(of: self.query) { | ||||||
|  |                             if query.count > 0 { | ||||||
|  |                                 search(for: self.query) | ||||||
|  |                             } else { | ||||||
|  |                                 self.locations = [] | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |  | ||||||
|                     //                                        .onAppear { |                     //                                        .onAppear { | ||||||
|                     //                                            // TODO: delete this, it's for debug only |                     //                                            // TODO: delete this, it's for debug only | ||||||
|                     //                                            search(for: self.query) |                     //                                            search(for: self.query) | ||||||
|                     //                                        } |                     //                                        } | ||||||
|                     .overlay { |                         .overlay { | ||||||
|                         HStack { |                             HStack { | ||||||
|                             Spacer() |                                 Spacer() | ||||||
|                             Image(systemName: "multiply.circle.fill") |                                 Image(systemName: "multiply.circle.fill") | ||||||
|                                 .foregroundStyle(.gray) |                                     .foregroundStyle(.gray) | ||||||
|                                 .onTapGesture { |                                     .onTapGesture { | ||||||
|                                     query = "" |                                         query = "" | ||||||
|                                 } |                                         viewModel.destination = nil | ||||||
|  |                                         viewModel.directions = [] | ||||||
|  |                                     } | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|  |                 } | ||||||
|  |                 .modifier(TextFieldGrayBackgroudColor()) | ||||||
|  |                 Spacer() | ||||||
|  |                 ScrollView { | ||||||
|  |                     ForEach(self.locations, id: \.identifier) { location in | ||||||
|  |                         SearchItemView(location: location, showSteps: $showSteps, viewModel: viewModel) | ||||||
|                     } |                     } | ||||||
|             } |  | ||||||
|             .modifier(TextFieldGrayBackgroudColor()) |  | ||||||
|             Spacer() |  | ||||||
|             ScrollView { |  | ||||||
|                 ForEach(self.locations, id: \.identifier) { location in |  | ||||||
|                     SearchItemView( |  | ||||||
|                         location: location, directions: $directions, stepLength: $stepLength, |  | ||||||
|                         showSteps: $showSteps, destination: $destination) |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             .padding() | ||||||
|  |             .interactiveDismissDisabled() | ||||||
|  |             .ignoresSafeArea() | ||||||
|         } |         } | ||||||
|         .padding() |  | ||||||
|         .interactiveDismissDisabled() |  | ||||||
|  |  | ||||||
|         .presentationDetents([.height(200), .large]) |  | ||||||
|         .presentationBackground(.regularMaterial) |  | ||||||
|         .presentationBackgroundInteraction(.enabled(upThrough: .large)) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     func search(for text: String) { |     func search(for text: String) { | ||||||
|   | |||||||
							
								
								
									
										206
									
								
								StepMap/UIKitMapView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								StepMap/UIKitMapView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | |||||||
|  | // | ||||||
|  | //  UIKitMapView.swift | ||||||
|  | //  StepMap | ||||||
|  | // | ||||||
|  | //  Created by Oliver Hnat on 13/05/2025. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import UIKit | ||||||
|  | import MapKit | ||||||
|  | import SwiftUI | ||||||
|  | import Combine | ||||||
|  |  | ||||||
|  | class UIKitMapView: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate { | ||||||
|  |     var locationManager: LocationManager | ||||||
|  |     var viewModel: ViewModel | ||||||
|  |     var oldDirections: [MKRoute] = [] | ||||||
|  |     var oldDestination: MKMapItemAnnotation? | ||||||
|  |     private var cancellables = Set<AnyCancellable>() | ||||||
|  |     let searchViewConctroller: UIHostingController<SearchView> | ||||||
|  |     var annotationViewController: UIHostingController<AnnotationView>? | ||||||
|  |     let mapView : MKMapView = { | ||||||
|  |         let map = MKMapView() | ||||||
|  |         map.showsUserTrackingButton = true | ||||||
|  |         map.showsUserLocation = true | ||||||
|  |         map.selectableMapFeatures = .pointsOfInterest | ||||||
|  |         map.pitchButtonVisibility = .adaptive | ||||||
|  |         return map | ||||||
|  |     }() | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     init(locationManager: LocationManager, viewModel: ViewModel) { | ||||||
|  |         self.locationManager = locationManager | ||||||
|  |         self.viewModel = viewModel | ||||||
|  |         self.searchViewConctroller = UIHostingController(rootView: SearchView(locationManager: locationManager, viewModel: viewModel)) | ||||||
|  |         super.init(nibName: nil, bundle: nil) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     required init?(coder: NSCoder) { | ||||||
|  |         fatalError("init(coder:) has not been implemented") | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     override func viewDidLoad() { | ||||||
|  |         super.viewDidLoad() | ||||||
|  |         bindViewModel() | ||||||
|  |          | ||||||
|  |         mapView.delegate = self | ||||||
|  |         setMapConstraints() | ||||||
|  |         setLocation() | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     override func viewDidAppear(_ animated: Bool) { | ||||||
|  |         showSearchView() | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     private func setLocation() { | ||||||
|  |         locationManager.requestAuthorization() | ||||||
|  |         locationManager.requestLocation() | ||||||
|  |         if let userLocation = locationManager.location { | ||||||
|  |             let viewRegion = MKCoordinateRegion(center: userLocation, latitudinalMeters: 2000, longitudinalMeters: 2000) | ||||||
|  |             mapView.setRegion(viewRegion, animated: true) | ||||||
|  |         } | ||||||
|  |         mapView.setUserTrackingMode(.follow, animated: true) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     private func setMapConstraints() { | ||||||
|  |         view.addSubview(mapView) | ||||||
|  |         mapView.translatesAutoresizingMaskIntoConstraints = false | ||||||
|  |         mapView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true | ||||||
|  |         mapView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true | ||||||
|  |         mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true | ||||||
|  |         mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     private func showSearchView() { | ||||||
|  |         searchViewConctroller.view.backgroundColor = .clear | ||||||
|  |         searchViewConctroller.modalPresentationStyle = .pageSheet | ||||||
|  |         searchViewConctroller.edgesForExtendedLayout = [.top, .bottom, .left, .right] | ||||||
|  |         if let sheet = searchViewConctroller.sheetPresentationController { | ||||||
|  |             let smallDetentId = UISheetPresentationController.Detent.Identifier("small") | ||||||
|  |             let smallDetent = UISheetPresentationController.Detent.custom(identifier: smallDetentId) { context in | ||||||
|  |                 return 200 | ||||||
|  |             } | ||||||
|  |             sheet.detents = [smallDetent, .large()] | ||||||
|  |             sheet.largestUndimmedDetentIdentifier = .large | ||||||
|  |             sheet.prefersScrollingExpandsWhenScrolledToEdge = false | ||||||
|  |             sheet.prefersGrabberVisible = true | ||||||
|  |             sheet.prefersEdgeAttachedInCompactHeight = true | ||||||
|  |             sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true | ||||||
|  |         } | ||||||
|  |         self.present(searchViewConctroller, animated: true, completion: nil) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     private func refreshRoute() { | ||||||
|  |         DispatchQueue.main.async { | ||||||
|  |             for route in self.oldDirections { | ||||||
|  |                 self.mapView.removeOverlay(route.polyline) | ||||||
|  |             } | ||||||
|  |             self.oldDirections = self.viewModel.directions | ||||||
|  |             for route in self.viewModel.directions { | ||||||
|  |                 self.mapView.addOverlay(route.polyline, level: .aboveRoads) | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             if let destination = self.oldDestination { | ||||||
|  |                 self.mapView.removeAnnotation(destination) | ||||||
|  |             } | ||||||
|  |             if let destination = self.viewModel.destination { | ||||||
|  |                 self.oldDestination = MKMapItemAnnotation(mapItem: destination) | ||||||
|  |                 self.mapView.addAnnotation(self.oldDestination!) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func mapView(_ mapView: MKMapView, rendererFor overlay: any MKOverlay) -> MKOverlayRenderer { | ||||||
|  |         let renderer = MKGradientPolylineRenderer(overlay: overlay) | ||||||
|  |         renderer.setColors(Defaults.routeColor, locations: []) | ||||||
|  |         renderer.lineCap = .round | ||||||
|  |         renderer.lineWidth = Defaults.routeWidth | ||||||
|  |         return renderer | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func mapView(_ mapView: MKMapView, didSelect annotation: any MKAnnotation) { | ||||||
|  |         hideSearchView() | ||||||
|  |         hideAnnotationView() | ||||||
|  |         showAnnotation(annotation: annotation) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func showAnnotation(annotation: MKAnnotation) { | ||||||
|  |         viewModel.showDetails = true | ||||||
|  |         let location = CLLocation(latitude: annotation.coordinate.latitude, | ||||||
|  |                                   longitude: annotation.coordinate.longitude) | ||||||
|  |         CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in | ||||||
|  |             guard let placemark = placemarks?.first, error == nil else { return } | ||||||
|  |              | ||||||
|  |             self.annotationViewController = UIHostingController(rootView: AnnotationView(pm: placemark, title: annotation.title as? String, coordinate: location, viewModel: self.viewModel)) | ||||||
|  |             if let avc = self.annotationViewController { | ||||||
|  |                 avc.view.backgroundColor = .clear | ||||||
|  |                 avc.modalPresentationStyle = .pageSheet | ||||||
|  |                 avc.edgesForExtendedLayout = [.top, .bottom, .left, .right] | ||||||
|  |                 if let sheet = avc.sheetPresentationController { | ||||||
|  |                     let smallDetentId = UISheetPresentationController.Detent.Identifier("small") | ||||||
|  |                     let smallDetent = UISheetPresentationController.Detent.custom(identifier: smallDetentId) { context in | ||||||
|  |                         return 350 | ||||||
|  |                     } | ||||||
|  |                     sheet.detents = [smallDetent, .large()] | ||||||
|  |                     sheet.largestUndimmedDetentIdentifier = .large | ||||||
|  |                     sheet.prefersScrollingExpandsWhenScrolledToEdge = false | ||||||
|  |                     sheet.prefersGrabberVisible = true | ||||||
|  |                     sheet.prefersEdgeAttachedInCompactHeight = true | ||||||
|  |                     sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true | ||||||
|  |                 } | ||||||
|  |                 self.present(avc, animated: true, completion: nil) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func mapView(_ mapView: MKMapView, didDeselect annotation: any MKAnnotation) { | ||||||
|  |         if viewModel.showDetails { | ||||||
|  |             viewModel.showDetails = false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func hideSearchView() { | ||||||
|  |         searchViewConctroller.dismiss(animated: true) | ||||||
|  |     } | ||||||
|  |     func hideAnnotationView() { | ||||||
|  |         if let avc = self.annotationViewController { | ||||||
|  |             avc.dismiss(animated: true) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     private func bindViewModel() { | ||||||
|  |         viewModel.$directions | ||||||
|  |             .receive(on: DispatchQueue.main) | ||||||
|  |             .sink { [weak self] _ in | ||||||
|  |                 self?.refreshRoute() | ||||||
|  |             } | ||||||
|  |             .store(in: &cancellables) | ||||||
|  |         viewModel.$showDetails | ||||||
|  |             .receive(on: DispatchQueue.main) | ||||||
|  |             .sink { [weak self] value in | ||||||
|  |                 print(value) | ||||||
|  |                 if !value { | ||||||
|  |                     self?.hideAnnotationView() | ||||||
|  |                     self?.showSearchView() | ||||||
|  | //                    self?.mapView.selectedAnnotations = [] | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .store(in: &cancellables) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct MapView: UIViewControllerRepresentable { | ||||||
|  |     typealias UIViewControllerType = UIKitMapView | ||||||
|  |     @StateObject var locationManager: LocationManager | ||||||
|  |     @ObservedObject var viewModel: ViewModel | ||||||
|  |  | ||||||
|  |     func makeUIViewController(context: Context) -> UIKitMapView { | ||||||
|  |         return UIKitMapView(locationManager: locationManager, viewModel: viewModel) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     func updateUIViewController(_ uiViewController: UIKitMapView, context: Context) { | ||||||
|  |         // pass | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -10,10 +10,10 @@ import MapKit | |||||||
| import SwiftUI | import SwiftUI | ||||||
|  |  | ||||||
| struct Defaults { | struct Defaults { | ||||||
|     static let routeColor: [Color] = [ |     static let routeColor: [UIColor] = [ | ||||||
|         .blue, |         .blue, | ||||||
|         .red, | //        .red, | ||||||
|         .yellow, | //        .yellow, | ||||||
|     ] |     ] | ||||||
|     static let routeWidth: CGFloat = 8 |     static let routeWidth: CGFloat = 8 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,10 +6,16 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| import Foundation | import Foundation | ||||||
|  | import MapKit | ||||||
|  |  | ||||||
| class ViewModel: ObservableObject { | class ViewModel: ObservableObject { | ||||||
|     @Published var test: String = UserDefaults.standard.string(forKey: "test") ?? "" |     @Published var test: String = UserDefaults.standard.string(forKey: "test") ?? "" | ||||||
|      |     @Published var directions: [MKRoute] = [] | ||||||
|  |     @Published var stepLength: Double? | ||||||
|  |     @Published var destination: MKMapItem? | ||||||
|  |     @Published var showDetails = false | ||||||
|  |  | ||||||
|  |  | ||||||
|     func saveValue(_ value: String) { |     func saveValue(_ value: String) { | ||||||
|         UserDefaults.standard.set(value, forKey: "test") |         UserDefaults.standard.set(value, forKey: "test") | ||||||
|         test = value |         test = value | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user