Search code examples
arraysswiftuilocation

SwiftUI. Trying to pass variables between views with @Binding but keep getting errors


I have this function that finds the closest location from an array of lat,long's to a users location.

func getClosest(){
let LRK = CLLocation(latitude: 37.944804, longitude: -122.509066)
let SAU = CLLocation(latitude: 37.856315, longitude: -122.478723)
let SFO = CLLocation(latitude: 37.795346, longitude: -122.392711)
//let TEST = CLLocation(latitude: 37.947112, longitude: -122.490452)

let coordinates = [LRK, SAU, SFO]

let userLocation = CLLocation(latitude: 37.944804, longitude: -122.509066)

@State var closest = coordinates.min(by:
    { $0.distance(from: userLocation) < $1.distance(from: userLocation) })

print("Closest from Function", (closest))

}

In the main swift file I am trying to use that location as the destination and then drawing a polyline between the two.

import SwiftUI
import MapKit

@Observable
class NewLocationManager {
    var location: CLLocation? = nil
    private let locationManager = CLLocationManager()
    
    func requestUserAuthorization() async throws {
        locationManager.requestWhenInUseAuthorization()
    }
    
    func startCurrentLocationUpdates() async throws {
        for try await locationUpdate in CLLocationUpdate.liveUpdates() {
            // adjust the logic/parameters as you require
            let oldLocation = self.location == nil ? CLLocation() : self.location!
            guard let newLocation = locationUpdate.location else { return }
            if !oldLocation.isClose(to: newLocation, withinDistance: 50.0) {
                self.location = newLocation

            }
        }
    }
}

func getClosest(){
    let LRK = CLLocation(latitude: 37.944804, longitude: -122.509066)
    let SAU = CLLocation(latitude: 37.856315, longitude: -122.478723)
    let SFO = CLLocation(latitude: 37.795346, longitude: -122.392711)
    //let TEST = CLLocation(latitude: 37.947112, longitude: -122.490452)

    let coordinates = [LRK, SAU, SFO]

    let userLocation = CLLocation(latitude: 37.944804, longitude: -122.509066)

    @State var closest = coordinates.min(by:
        { $0.distance(from: userLocation) < $1.distance(from: userLocation) })
    
    print("Closest from Function", (closest))
}

struct ContentView: View {
    @State var newlocationManager = NewLocationManager()
    @State private var selectedResult: MKMapItem?
    @State private var route: MKRoute?
  
    private let destination = CLLocationCoordinate2D(latitude: 38.944804, longitude: -122.509066)
    
    @State private var myLocation = CLLocationCoordinate2D(latitude: 35.67, longitude: 139.763)  // <--- here @State
    
    var body: some View {
   
        Map(selection: $selectedResult) {
            UserAnnotation()
            // Adding the marker for the starting point
            Marker("Start", coordinate: destination)
            // Show the route if it is available
            if let route {
                MapPolyline(route)
                    .stroke(.blue, lineWidth: 5)
            }
        }
        .onChange(of: newlocationManager.location) {  // <--- here
            if let coord = newlocationManager.location?.coordinate {
                myLocation = coord
                getDirections2()
                
            }
        }
        .task {
            try? await newlocationManager.requestUserAuthorization()
            try? await newlocationManager.startCurrentLocationUpdates()
            // remember that nothing will run here until the for try await loop finishes
            
        }
        .onAppear {
            CLLocationManager().requestWhenInUseAuthorization()
            selectedResult = MKMapItem(placemark: MKPlacemark(coordinate: myLocation))
            
        }
        
    }
    
    // --- here
    func getDirections2() {
        route = nil
        // Create and configure the request
        let request = MKDirections.Request()
        request.source = MKMapItem(placemark: MKPlacemark(coordinate: destination))
        request.destination = MKMapItem(placemark: MKPlacemark(coordinate: myLocation))
        // Get the directions based on the request
        Task {
            let directions = MKDirections(request: request)
            let response = try? await directions.calculate()
            route = response?.routes.first
        }
    }
    
}

extension CLLocation {
    func isClose(to otherLocation: CLLocation, withinDistance distance: CLLocationDistance) -> Bool {
        return self.distance(from: otherLocation) <= distance
    }
}


#Preview {
    ContentView()
}

What I cannot do is get the "userLocation" into the function and then get the closest variable result from that function to be the "destination" for drawing the polyline. Any ideas?


Solution

  • This should do the trick. I also improved your code a bit to make it cleaner. I don't really know what your code is supposed to do so let me know if it works.

    import SwiftUI
    import MapKit
    import CoreLocation
    
    // Define the function to get the closest location
    func getClosest(userLocation: CLLocation, locations: [CLLocation]) -> CLLocation? {
        return locations.min(by: { $0.distance(from: userLocation) < $1.distance(from: userLocation) })
    }
    
    // Observable Location Manager class
    class NewLocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
        @Published var location: CLLocation? = nil
        private let locationManager = CLLocationManager()
        
        override init() {
            super.init()
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
            locationManager.requestWhenInUseAuthorization()
            locationManager.startUpdatingLocation()
        }
        
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            guard let newLocation = locations.last else { return }
            self.location = newLocation
        }
    }
    
    // SwiftUI View
    struct ContentView: View {
        @StateObject var newLocationManager = NewLocationManager()
        @State private var selectedResult: MKMapItem?
        @State private var route: MKRoute?
        @State private var destination: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 37.944804, longitude: -122.509066) // Default value
        
        let locations = [
            CLLocation(latitude: 37.944804, longitude: -122.509066), // LRK
            CLLocation(latitude: 37.856315, longitude: -122.478723), // SAU
            CLLocation(latitude: 37.795346, longitude: -122.392711)  // SFO
        ]
        
        var body: some View {
            Map {
                if let userLocation = newLocationManager.location {
                    Marker("Start", coordinate: destination)
                    Marker("User", coordinate: userLocation.coordinate)
                    
                    if let route = route {
                        MapPolyline(route)
                            .stroke(.blue, lineWidth: 5)
                    }
                }
            }
            .onChange(of: newLocationManager.location) {
                if let userLocation = newLocationManager.location {
                    if let closestLocation = getClosest(userLocation: userLocation, locations: locations) {
                        destination = closestLocation.coordinate
                        getDirections()
                    }
                }
            }
        }
        
        func getDirections() {
            guard let userLocation = newLocationManager.location else { return }
            
            let request = MKDirections.Request()
            request.source = MKMapItem(placemark: MKPlacemark(coordinate: userLocation.coordinate))
            request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destination))
            
            Task {
                let directions = MKDirections(request: request)
                do {
                    let response = try await directions.calculate()
                    route = response.routes.first
                } catch {
                    print("Error getting directions: \(error)")
                }
            }
        }
    }
    
    extension CLLocation {
        func isClose(to otherLocation: CLLocation, withinDistance distance: CLLocationDistance) -> Bool {
            return self.distance(from: otherLocation) <= distance
        }
    }
    
    #Preview {
        ContentView()
    }