Simplifying code using arrays in Swift (core location) - ios

I am doing little project: If person is near given coordinates it outputs "Hello". It is working but I think it is too much code if I keep making so many variables. Is there a way to organize it or make it easier to add coordinates. I tried using array but can't get it to work. What could be the best solution? I am pretty much new to Swift syntax. Any help is much appreciated.
let addressLocation:CLLocation = CLLocation(latitude: 37.334421, longitude: -122.037987)
let addressLocation2:CLLocation = CLLocation(latitude: 37.334395, longitude: -122.041173)
let meters:CLLocationDistance = location!.distanceFromLocation(addressLocation)
let meters2:CLLocationDistance = location!.distanceFromLocation(addressLocation2)
if (meters <= 50.00000000 || meters2 <= 50.0000000){
print("Hello")
}else{
print("No")
}

This is how you would use an array to hold the locations.
var location = CLLocation(latitude: 37.334421, longitude: -122.037987)
var locations:[CLLocation] = [CLLocation(latitude: 37.334421, longitude: -122.037987),
CLLocation(latitude: 37.334395, longitude: -122.041173)]
var distances = locations.map{location.distanceFromLocation($0)}
let maxDistance = 50.0
if (distances.contains{$0 < maxDistance}) {
print("Hello")
} else {
print("No")
}

If needed, you can get an array of all close locations with locations.filter({ location!.distanceFromLocation($0) < maxDistance }), however, for your purpose, this is the simplest way I could think of:
var locations = [CLLocation(latitude: 37.334421, longitude: -122.037987),
CLLocation(latitude: 37.334395, longitude: -122.041173)]
let maxDistance = 50.0
if locations.contains({ location!.distanceFromLocation($0) < maxDistance }) {
print("Hello")
} else {
print("No close locations")
}

This is how you implement a function that receives a given location, a list of addresses and perform your check.
func check(location:CLLocation?, addresses:[CLLocation]) {
guard let location = location else { return }
if (addresses.contains { $0.distanceFromLocation(location) < 50 }) {
print("Hello")
} else {
print("No")
}
}

Related

Calculating trip distance core location swift

I have an application where I calculate distance travelled like the Uber application. When a driver starts a trip, the location begins to change even though a start point has been specified in the search for a ride, a driver could decide to pass an alternative route or pass long places and routes because he/ she does not know the shortest route, how then do I calculate the total distance.
The starting location is the location the driver hits start button
The end location is the location the driver hits stop button
this is my code so far
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
lastLocation = locations.last!
endTrip(locations.last)
if !hasSetInitialLocation {
let camera = GMSCameraPosition.camera(withTarget: lastLocation!.coordinate, zoom: 17)
self.mapView.animate(to: camera)
hasSetInitialLocation = true
endTrip(lastLocation)
MqttManager.instance.connectToServer()
}
}
func endTrip(endLoaction: CLLocation) {
guard let statusChange = source.getStatusChange() else{return}
var distanceTraveled: Double = 0.0
let initialLocation = CLLocation(latitude: (statusChange.meta?.location?.lat)!, longitude: (statusChange.meta?.location?.lng)!)
let distance = initialLocation.distance(from: endLoaction)
distanceTraveled += distance
let distanceInKM = Utility.convertCLLocationDistanceToKiloMeters(targetDistance: distanceTraveled)
}
How can i calculate the distance to reflect the total distance moved by the driver since there could be a change in route from the proposed start point and end point.
The driver hits a button called start trip, I want to get the distance from that moment till the moment he hits the button end trip
this implementation could be got from a similar working code like these but the only difference is that their is a start button which passes the coordinates at that point and a stop coordinate which is the end of the coordinate.
enum DistanceValue: Int {
case meters, miles
}
func calculateDistanceBetweenLocations(_ firstLocation: CLLocation, secondLocation: CLLocation, valueType: DistanceValue) -> Double {
var distance = 0.0
let meters = firstLocation.distance(from: secondLocation)
distance += meters
switch valueType {
case .meters:
return distance
case .miles:
let miles = distance
return miles
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if startLocation == nil {
startLocation = locations.first
} else if let location = locations.last {
runDistance += lastLocation.distance(from: location)
let calc = calculateDistanceBetweenLocations(lastLocation, secondLocation: location, valueType: .meters)
print("TOTAL LOC 1 \(calc)")
print("TOTAL LOC 2 \(runDistance)")
}
lastLocation = locations.last
}
as shown in my print statements print("TOTAL LOC 1 \(calc)")
print("TOTAL LOC 2 \(runDistance)") how can I make
calc the same with runDistance
here is what is printed in the console
TOTAL LOC 10.29331530774379
TOTAL LOC 2 10.29331530774379
TOTAL LOC 2.2655118031831587
TOTAL LOC 2 12.558827110926948
If you get the distance like this using the first and last coordinate it always returns the wrong value because it can't identify the actual traveling path.
I did resolve the same issue with using the following code.
use GoogleMaps
> pod 'GoogleMaps'
Make the coordinates array while the driver is moving on a route.
var arr = [Any]()
// Driving lat long co-ordinateds continues add in this array according to your expectation either update location or perticuler time duration.
// make GMSMutablePath of your co-ordinates
let path = GMSMutablePath()
for obj in arr{
print(obj)
if let lat = (obj as? NSDictionary)?.value(forKey: PARAMETERS.LET) as? String{
path.addLatitude(Double(lat)!, longitude: Double(((obj as? NSDictionary)?.value(forKey: PARAMETERS.LONG) as? String)!)!)
}
}
print(path) // Here is your traveling path
let km = GMSGeometryLength(path)
print(km) // your total traveling distance.
I did it in this app and it's working fine.
Hope it will helps you :)
OR without GoogleMaps
You have to come with locations, an array of CLLocationCoordinate2D, for yourself, as per your code, though.
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
// MARK: - Variables
let locationManager = CLLocationManager()
// MARK: - IBOutlet
#IBOutlet weak var mapView: MKMapView!
// MARK: - IBAction
#IBAction func distanceTapped(_ sender: UIBarButtonItem) {
let locations: [CLLocationCoordinate2D] = [...]
var total: Double = 0.0
for i in 0..<locations.count - 1 {
let start = locations[i]
let end = locations[i + 1]
let distance = getDistance(from: start, to: end)
total += distance
}
print(total)
}
func getDistance(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) -> CLLocationDistance {
// By Aviel Gross
// https://stackoverflow.com/questions/11077425/finding-distance-between-cllocationcoordinate2d-points
let from = CLLocation(latitude: from.latitude, longitude: from.longitude)
let to = CLLocation(latitude: to.latitude, longitude: to.longitude)
return from.distance(from: to)
}
}
Output
A simple function to calculate distance (in meters) given an array of CLLocationCoordinate2D. Uses reduce instead of array iteration.
func computeDistance(from points: [CLLocationCoordinate2D]) -> Double {
guard let first = points.first else { return 0.0 }
var prevPoint = first
return points.reduce(0.0) { (count, point) -> Double in
let newCount = count + CLLocation(latitude: prevPoint.latitude, longitude: prevPoint.longitude).distance(
from: CLLocation(latitude: point.latitude, longitude: point.longitude))
prevPoint = point
return newCount
}
}
I like to use an extension for that
extension Array where Element: CLLocation {
var distance: Double {
guard count > 1 else { return 0 }
var previous = self[0]
return reduce(0) { (result, location) -> Double in
let distance = location.distance(from: previous)
previous = location
return result + distance
}
}
}
Usage:
locations.distance

I get an empty CLLocationCoordinates array when loading data from user defaults

I'm trying to store to UserDefaults an array of CCLocationCoordinates from the tracking portion of my app paired with the name of the tracked route as key, to be able to recall it later on to use it within a function.
The problem is that when I call that function I get the index out of range error. I checked and the array is empty.
As I'm new to user defaults I tried to see other similar posts but they're all about NSUserDefaults and didn't find a solution.
Heres the code for the functions for storing and recalling the array:
func stopTracking2() {
self.trackingIsActive = false
self.trackigButton.backgroundColor = UIColor.yellow
locationManager.stopUpdatingLocation()
let stopRoutePosition = RouteAnnotation(title: "Route Stop", coordinate: (locationManager.location?.coordinate)!, imageName: "Route Stop")
self.actualRouteInUseAnnotations.append(stopRoutePosition)
print(actualRouteInUseCoordinatesArray)
print(actualRouteInUseAnnotations)
drawRoutePolyline() // draw line to show route
// checkAlerts2() // check if there is any notified problem on our route and marks it with a blue circle, now called at programmed checking
saveRouteToUserDefaults()
postRouteToAnalitics() // store route anonymously to FIrebase
}
func saveRouteToUserDefaults() {
// save actualRouteInUseCoordinatesArray : change for function
// userDefaults.set(actualRouteInUseCoordinatesArray, forKey: "\(String(describing: userRoute))")
storeCoordinates(actualRouteInUseCoordinatesArray)
}
// Store an array of CLLocationCoordinate2D
func storeCoordinates(_ coordinates: [CLLocationCoordinate2D]) {
let locations = coordinates.map { coordinate -> CLLocation in
return CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
}
let archived = NSKeyedArchiver.archivedData(withRootObject: locations)
userDefaults.set(archived, forKey: "\(String(describing: userRoute))")
userDefaults.synchronize()
}
func loadRouteFromUserDefaults() {
// gets entry from userRouteArray stored in userDefaults and append them into actualRouteInUseCoordinatesArray
actualRouteInUseCoordinatesArray.removeAll()
actualRouteInUseCoordinatesArray = userDefaults.object(forKey: "\(String(describing: userRoute))") as? [CLLocationCoordinate2D] ?? [CLLocationCoordinate2D]() // here we get the right set of coordinates for the route we are about to do the check on
// load route coordinates from UserDefaults
// actualRouteInUseCoordinatesArray = loadCoordinates()! //error found nil
}
// Return an array of CLLocationCoordinate2D
func loadCoordinates() -> [CLLocationCoordinate2D]? {
guard let archived = userDefaults.object(forKey: "\(String(describing: userRoute))") as? Data,
let locations = NSKeyedUnarchiver.unarchiveObject(with: archived) as? [CLLocation] else {
return nil
}
let coordinates = locations.map { location -> CLLocationCoordinate2D in
return location.coordinate
}
return coordinates
}
}
extension NewMapViewController {
// ALERTS :
func checkAlerts2() {
loadRouteFromUserDefaults() //load route coordinates to check in
// CHECK IF ANY OBSTACLE IS OUN OUR ROUTE BY COMPARING DISTANCES
while trackingCoordinatesArrayPosition != ( (actualRouteInUseCoordinatesArray.count) - 1) {
print("checking is started")
print(actualRouteInUseCoordinatesArray)
let trackingLatitude = actualRouteInUseCoordinatesArray[trackingCoordinatesArrayPosition].latitude
let trackingLongitude = actualRouteInUseCoordinatesArray[trackingCoordinatesArrayPosition].longitude
let alertLatitude = alertNotificationCoordinatesArray[alertNotificationCoordinatesArrayPosition].latitude
let alertLongitude = alertNotificationCoordinatesArray[alertNotificationCoordinatesArrayPosition].longitude
let coordinateFrom = CLLocation(latitude: trackingLatitude, longitude: trackingLongitude)
let coordinateTo = CLLocation(latitude: alertLatitude, longitude: alertLongitude)
let coordinatesDistanceInMeters = coordinateFrom.distance(from: coordinateTo)
// CHECK SENSITIVITY: sets the distance in meters for an alert to be considered an obstacle
if coordinatesDistanceInMeters <= 10 {
print( "found problem")
routeObstacle.append(alertNotificationCoordinatesArray[alertNotificationCoordinatesArrayPosition]) // populate obstacles array
trackingCoordinatesArrayPosition = ( trackingCoordinatesArrayPosition + 1)
}
else if alertNotificationCoordinatesArrayPosition < ((alertNotificationCoordinatesArray.count) - 1) {
alertNotificationCoordinatesArrayPosition = alertNotificationCoordinatesArrayPosition + 1
}
else if alertNotificationCoordinatesArrayPosition == (alertNotificationCoordinatesArray.count - 1) {
trackingCoordinatesArrayPosition = ( trackingCoordinatesArrayPosition + 1)
alertNotificationCoordinatesArrayPosition = 0
}
}
findObstacles()
NewMapViewController.checkCounter = 0
displayObstacles()
}
In the extension you can see the function that uses the array.
Right after the print of the array I get the index out of range error.
Thanks as usual to the community.
After trying various solutions offered I decided to rewrite the whole thing.
So after finding a post on how to code/decode my array to string I decided it was the way to go. It shouldn't be heavy on the system as it's a string that gets saved. Please let me know what you think of this solution.
Thank to #Sh_Khan to point out it was a decoding issue, and to #Moritz to point out I was performing a bad practice.
So the code is:
func storeRoute() {
// first we code the CLLocationCoordinate2D array to string
// second we store string into userDefaults
userDefaults.set(encodeCoordinates(coords: actualRouteInUseCoordinatesArray), forKey: "\(String(describing: NewMapViewController.userRoute))")
}
func loadRoute() {
//first se load string from user defaults
let route = userDefaults.string(forKey: "\(String(describing: NewMapViewController.userRoute))")
print("loaded route is \(route!))")
//second we decode it into CLLocationCoordinate2D array
actualRouteInUseCoordinatesArray = decodeCoordinates(encodedString: route!)
print("decoded route array is \(actualRouteInUseCoordinatesArray))")
}
func encodeCoordinates(coords: [CLLocationCoordinate2D]) -> String {
let flattenedCoords: [String] = coords.map { coord -> String in "\(coord.latitude):\(coord.longitude)" }
let encodedString: String = flattenedCoords.joined(separator: ",")
return encodedString
}
func decodeCoordinates(encodedString: String) -> [CLLocationCoordinate2D] {
let flattenedCoords: [String] = encodedString.components(separatedBy: ",")
let coords: [CLLocationCoordinate2D] = flattenedCoords.map { coord -> CLLocationCoordinate2D in
let split = coord.components(separatedBy: ":")
if split.count == 2 {
let latitude: Double = Double(split[0]) ?? 0
let longitude: Double = Double(split[1]) ?? 0
return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
} else {
return CLLocationCoordinate2D()
}
}
return coords
}
Rather than using heavy-weight objectiv-c-ish NSKeyed(Un)Archiver and making a detour via CLLocation I recommend to extend CLLocationCoordinate2D to adopt Codable
extension CLLocationCoordinate2D : Codable {
public init(from decoder: Decoder) throws {
var arrayContainer = try decoder.unkeyedContainer()
if arrayContainer.count == 2 {
let lat = try arrayContainer.decode(CLLocationDegrees.self)
let lng = try arrayContainer.decode(CLLocationDegrees.self)
self.init(latitude: lat, longitude: lng)
} else {
throw DecodingError.dataCorruptedError(in: arrayContainer, debugDescription: "Coordinate array must contain two items")
}
}
public func encode(to encoder: Encoder) throws {
var arrayContainer = encoder.unkeyedContainer()
try arrayContainer.encode(contentsOf: [latitude, longitude])
}
}
and replace the methods to load and save data with
func storeCoordinates(_ coordinates: [CLLocationCoordinate2D]) throws {
let data = try JSONEncoder().encode(coordinates)
UserDefaults.standard.set(data, forKey: String(describing: userRoute))
}
func loadCoordinates() -> [CLLocationCoordinate2D] {
guard let data = UserDefaults.standard.data(forKey: String(describing: userRoute)) else { return [] }
do {
return try JSONDecoder().decode([CLLocationCoordinate2D].self, from: data)
} catch {
print(error)
return []
}
}
storeCoordinates throws it hands over a potential encoding error
Load the data with
actualRouteInUseCoordinatesArray = loadCoordinates()
and save it
do {
try storeCoordinates(actualRouteInUseCoordinatesArray)
} catch { print(error) }
Your problem is that you save it as data and try to read directly without unarchiving , You can try
let locations = [CLLocation(latitude: 123, longitude: 344),CLLocation(latitude: 123, longitude: 344),CLLocation(latitude: 123, longitude: 344)]
do {
let archived = try NSKeyedArchiver.archivedData(withRootObject: locations, requiringSecureCoding: true)
UserDefaults.standard.set(archived, forKey:"myKey")
// read savely
if let data = UserDefaults.standard.data(forKey: "myKey") {
let saved = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [CLLocation]
print(saved)
}
}
catch {
print(error)
}

How do I avoid pyramid of doom - iOS?

I have a pyramid of doom in my code.
if places.count > 0 {
for i in 0..<places.count {
for j in 0..<places.count {
if let nameI = places[i]["name"] {
if let cityI = places[i]["city"] {
if let nameJ = places[j]["name"] {
if let cityJ = places[j]["city"] {
if let latI = places[i]["lat"] {
if let lonI = places[i]["lon"] {
if let latitudeI = Double(latI) {
if let longitudeI = Double(lonI) {
if let latJ = places[j]["lat"] {
if let lonJ = places[j]["lon"] {
if let latitudeJ = Double(latJ) {
if let longitudeJ = Double(lonJ) {
if(i != j) {
let coordinateI = CLLocation(latitude: latitudeI, longitude: longitudeI)
let coordinateJ = CLLocation(latitude: latitudeJ, longitude: longitudeJ)
let distanceInMeters = coordinateI.distance(from: coordinateJ) // result is in meters
let distanceInMiles = distanceInMeters/1609.344
var distances = [Distance]()
distances.append(Distance(
distanceInMiles: distanceInMiles,
distanceInMeters: distanceInMeters,
places: [
Place(name: nameI, city: cityI, lat: latitudeI, long: longitudeI, coordinate: coordinateI),
Place(name: nameJ, city: cityJ, lat: latitudeJ, long: longitudeJ, coordinate: coordinateJ),
]
))
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
How do I avoid it?
Is there a technique or rule I should follow?
In iOS, we have to use a lot of if-let. How would one avoid doing like me ?
Here's a first approximation. As you can see, there are lots of repeated patterns that can be extracted out.
for (i, placeDictI) in 0..<places.enumerated() {
guard
let nameI = placeDictI["name"],
let cityI = placeDictI["city"],
let latitudeI = placeDictI["lat"].map(Double.init),
let longitudeI = placeDictI["lon"].map(Double.init),
else { continue }
let coordinateI = CLLocation(latitude: latitudeI, longitude: longitudeI)
let placeI = Place(name: nameI, city: cityI, lat: latitudeI, long: longitudeI, coordinate: coordinateI)
for (j, placeDictJ) in places.enumerated() where i != j {
guard let nameJ = placeDictI["name"],
let cityJ = placeDictI["city"],
let latitudeJ = placeDictI["lat"].map(Double.init),
let longitudeJ = placeDictI["lon"].map(Double.init)
else { continue }
let coordinateJ = CLLocation(latitude: latitudeJ, longitude: longitudeJ)
let placeJ = Place(name: nameJ, city: cityJ, lat: latitudeJ, long: longitudeJ, coordinate: coordinateJ)
let distanceInMeters = coordinateI.distance(from: coordinateJ) // result is in meters // Editor's note: REALLY? I would have thought that a variable called "distanceInMeters" would store volume in litres! Silly me!
let distanceInMiles = distanceInMeters/1609.344
var distances = [Distance]()
distances.append(Distance(
distanceInMiles: distanceInMiles,
distanceInMeters: distanceInMeters,
places: [ placeI, placeJ ]
))
}
}
Here are the transformations I applied:
Don't check for places.count > 0. If it's 0, the loop won't do anything.
For every if statement whose block fully encompasses its parent block, I replaced it with a guard.
Merged adjacent guard statements with a comma.
Expressed failable type conversions as Optional.map(_:) expressions, rather than as separate let clauses in the guard statement
Changed the i != j check into a where condition on the inner for loop.
Changed this pattern:
for i in 0..<array.count {
use(array[i])
use(array[i])
use(array[i])
//...
}
to this pattern:
for (i, element) in array.enumerated() {
use(element)
use(element)
use(element)
//...
}
Seeing as there's no an obvious case of repetition, it's a good hint that the Dictionary unpacking into a Place belongs in a convenience initializer, that takes a dict that initailizes a Place? from it. Even better, just use the Codable system, and have the compiler synthesize it for you.

How to Set UITabBar background image change?

How can i sort the array based on distance from current location and show in tableview .when i use sorting am not getting any proper results ,am getting the array with random distance.can any one guide me for solve this issue
To sort locations based on distance from current location in best possible way would be have location points in form of struct
struct LocationPoints {
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
func distance(to currentLocation: CLLocation) -> CLLocationDistance {
return CLLocation(latitude: self.latitude, longitude: self.longitude).distance(from: currentLocation)
}
}
Let suppose you have an array of LocationPoints having latitude & longitude
var coordinates: [LocationPoints] = []
coordinates.append(LocationPoints(latitude: Double(25), longitude: Double(24)))
coordinates.append( LocationPoints(latitude: Double(23), longitude: Double(22)))
sort function
coordinates = sortLocationsWithCurrentLocation(locations: coordinates, currentLocation: CLLocation(latitude: Double(20), longitude: Double(21)))
func sortLocationsWithCurrentLocation(locations:[LocationPoints],currentLocation:CLLocation) -> [LocationPoints] {
//set here current position as current location
let currentPosition : CLLocation = CLLocation(latitude: 30, longitude: 24)
let sortedLocations = locations.sorted(by: { (point1 : LocationPoints, point2 :LocationPoints) -> Bool in
if point1.distance(to: currentPosition) < point2.distance(to: currentPosition)
{
return true
}
return false
})
return sortedLocations
}

Loop through coordinates and find the closest shop to a point Swift 3

Idea :
App lets drivers see the closest shop/restaurants to customers.
What I have :
Coordinates saved as strings
let clientLat = "24.449384"
let clientLng = "56.343243"
a function to find all the shops in my local area
I tried to save all the coordinates of a shop in my local area and I succeeded:
var coordinates: [CLLocationCoordinate2D] = [CLLocationCoordinate2D]()
func performSearch() {
coordinates.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "starbucks"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
self.coordinates.append(item.placemark.coordinate)
// need to sort coordinates
// need to find the closest
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
self.mapView.addAnnotation(annotation)
}
}
})
}
What I need:
I wish to loop through the coordinates and find the closest shop (kilometers) to the lat and long strings then put a pin on it.
UPDATE
func performSearch() {
coordinates.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "starbucks"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
self.coordinates.append(item.placemark.coordinate)
let pointToCompare = CLLocation(latitude: 24.741721, longitude: 46.891440)
let storedCorrdinates = self.coordinates.map({CLLocation(latitude: $0.latitude, longitude: $0.longitude)}).sorted(by: {
$0.distance(from: pointToCompare) < $1.distance(from: pointToCompare)
})
self.coordinate = storedCorrdinates
}
let annotation = MKPointAnnotation()
annotation.coordinate = self.coordinate[0].coordinate
self.mapView.addAnnotation(annotation)
}
})
}
Thank you #brimstone
You can compare distances between coordinates by converting them to CLLocation types and then using the distance(from:) method. For example, take your coordinates array and map it to CLLocation, then sort that based on the distance from the point you are comparing them to.
let coordinates = [CLLocationCoordinate2D]()
let pointToCompare = CLLocation(latitude: <#yourLat#>, longitude: <#yourLong#>)
let sortedCoordinates = coordinates.map({CLLocation(latitude: $0.latitude, longitude: $0.longitude)}).sorted(by: {
$0.distance(from: pointToCompare) < $1.distance(from: pointToCompare)
})
Then, to set your annotation's coordinate to the nearest coordinate, just subscript the sortedCoordinates array.
annotation.coordinate = sortedCoordinates[0].coordinate
I would like to share my solution :)
1) In my case, I upload data from the API, so I need to create a model.
import MapKit
struct StoresMap: Codable {
let id: Int?
let title: String?
let latitude: Double?
let longitude: Double?
let schedule: String?
let phone: String?
let ukmStoreId: Int?
var distanceToUser: CLLocationDistance?
}
The last variable is not from API, but from myself to define distance for each store.
2) In ViewController I define:
func fetchStoresList() {
NetworkManager.downloadStoresListForMap(firstPartURL: backendURL) { (storesList) in
self.shopList = storesList
let initialLocation = self.locationManager.location!
for i in 0..<self.shopList.count {
self.shopList[i].distanceToUser = initialLocation.distance(from: CLLocation(latitude: self.shopList[i].latitude!, longitude: self.shopList[i].longitude!))
}
self.shopList.sort(by: { $0.distanceToUser! < $1.distanceToUser!})
print("Closest shop - ", self.shopList[0])
}
}
3) Don't forget to call the function in viewDidLoad() and import MapView framework :)

Resources