How do I Create a Loop in Swift?
Lately I've been learning Swift (in Xcode) and I'm trying to make an app. I have been following a tutorial to make a route in Mapkit.
Currently, I have it so that a user can type in a location and it creates a route to that location.
However, I want to make it so that the user can type in a distance, and a loop (like a running route) is generated on the map that starts and ends at the user's location. The loop will be as close as possible to the specified distance.
Hopefully that makes sense, here is my code:
//
// ContentView.swift
// NavNinja
//
// Created by Dakota on 2/11/24.
//
import SwiftUI
import MapKit
struct ContentView: View {
@State private var cameraPosition: MapCameraPosition = .region(.userRegion)
@State private var searchText = ""
@State private var results = [MKMapItem]()
@State private var mapSelection: MKMapItem?
@State private var showDetails = false
@State private var getDirections = false
//ROUTE
@State private var routeDisplaying = false
@State private var route: MKRoute?
@State private var routeDestination: MKMapItem?
@State private var routeDistanceInMiles: Double = 0.0
//ROUTE
var body: some View {
Map(position: $cameraPosition, selection: $mapSelection) {
UserAnnotation()
Annotation("My location", coordinate: .userLocation) {
ZStack {
Circle()
.frame(width: 32, height: 32)
.foregroundStyle(.blue.opacity(0.25))
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(.white)
Circle()
.frame(width: 12, height: 12)
.foregroundStyle(.blue)
}
}
ForEach(results, id: \.self) { item in
if routeDisplaying {
if item == routeDestination {
let placemark = item.placemark
Marker(placemark.name ?? "", coordinate: placemark.coordinate)
}
} else {
let placemark = item.placemark
Marker(placemark.name ?? "", coordinate: placemark.coordinate)
}
}
//ROUTE
if let route {
MapPolyline(route.polyline)
.stroke(.blue, lineWidth: 6)
}
//ROUTE
}
.overlay(alignment: .top) {
TextField("Search for a location...", text: $searchText)
.font(.subheadline)
.padding(22)
.background(.white)
.padding()
.shadow(radius: 10)
}
// Add route distance display
.overlay(alignment: .bottomTrailing) {
if route != nil {
Text(String(format: "Route Distance: %.2f miles", routeDistanceInMiles))
.font(.footnote)
.foregroundColor(.white)
.padding(8)
.background(Color.black.opacity(0.7))
.cornerRadius(8)
.padding(10)
}
}
.onSubmit(of: /*@START_MENU_TOKEN@*/.text/*@END_MENU_TOKEN@*/) {
Task { await searchPlaces() }
}
//ROUTE
.onChange(of: getDirections, { oddValue, newValue in
if newValue {
fetchRoute()
}
})
//ROUTE
.onChange(of: mapSelection, { addValue, newValue in
showDetails = newValue != nil
})
.sheet(isPresented: $showDetails, content: {
LocationDetailsView(mapSelection: $mapSelection,
show: $showDetails,
getDirections: $getDirections)
.presentationDetents([.height(340)])
.presentationBackgroundInteraction(.enabled(upThrough: .height(340)))
.presentationCornerRadius(12)
})
.mapControls {
MapCompass()
MapPitchToggle()
MapUserLocationButton()
}
}
}
extension ContentView {
func searchPlaces() async {
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchText
request.region = .userRegion
let results = try? await MKLocalSearch(request: request).start()
self.results = results?.mapItems ?? []
}
//ROUTE
func fetchRoute() {
if let mapSelection {
let request = MKDirections.Request()
request.source = MKMapItem(placemark: .init(coordinate: .userLocation))
request.destination = mapSelection
Task {
let result = try? await MKDirections(request: request).calculate()
route = result?.routes.first
routeDestination = mapSelection
withAnimation(.snappy) {
routeDisplaying = true
showDetails = false
if let rect = route?.polyline.boundingMapRect, routeDisplaying {
cameraPosition = .rect(rect)
}
// Calculate the total distance of the route in miles
if let totalDistanceInMeters = route?.distance {
// Convert meters to miles
let totalDistanceInMiles = totalDistanceInMeters / 1609.34 // 1 mile = 1609.34 meters
routeDistanceInMiles = totalDistanceInMiles
}
}
}
}
}
//ROUTE
}
extension CLLocationCoordinate2D {
static var userLocation: CLLocationCoordinate2D {
return .init(latitude: 25.7602, longitude: -80.1959)
}
}
extension MKCoordinateRegion {
static var userRegion: MKCoordinateRegion {
return .init(center: .userLocation,
latitudinalMeters: 1000,
longitudinalMeters: 1000)
}
}
#Preview {
ContentView()
}
All code between two "//ROUTE" comments indicates that the code has to do with mapping the route.