distanceInMeters Problems with arrays and sort
Use of unresolved identifier 'mall'
=> There is nothing named "mall" in the scope of the function locationManager didUpdateLocations
. Meaning: locationManager didUpdateLocations
has no clue what happens inside of tableView cellForRowAt indexPath
and vice versa.
You could solve this problem by applying following steps to your code:
- Move the distance calculation from
locationManager didUpdateLocations
totableView cellForRowAt indexPath
add a property to your class that stores the current position:
var currentPosition: CLLocation? = nil
Store the position you receive in
locationManager didUpdateLocations
to thecurrentPosition
property- Add
self.tableView.reloadData()
after you received and assigned the position - Use the
mall
and thecurrentPosition
incellForRowAt
to calculate the distance and update thedistanceLabel
- Keep in mind that
currentPosition
can benil
and the distance can not be calculated => make label empty or add something like "unknown distance" to it
Sort by distance Swift 3
Sort the malls first, and then use it as TableView's datasource, TableView will display the cells sorted as the order in Array sorted malls:
let sortedMalls = malls.sorted { (mallOne, mallTwo) -> Bool in
return mallOne.distance < mallTwo.distance
}
Also a one liner:
let sortedMalls = malls.sorted { $0.mallOne.distance < $1.mallTwo.distance }
Use sortedMalls for:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
How to make all objects have different distanceInMeters?
You have two big problems in your cellForRowAt
method.
coords
is an emptyCLLocation
, not the user's current location. You should store the user's current location in a property and use that property instead of thecoords
local variable.- You use
malls[0]
instead ofmall
to createmallLocate
. This is why all 100 objects appear to have the same coordinates.
Append array data into Struct variable
There's a lot of stuff going on here, so I'll walk you through the solution.
First of all, rest
is a poor variable name. When I read it first, I thought this was "the rest", like the remainder of something, and was looking for where the "main" data is. Keystrokes don't cost you $, you can afford to just type out restaurants
.
Secondly, you create an empty array, and manually append all these restaurants to it. Instead, you can just create an array from an array literal, that contains all the restaurants directly. That omits the need to have separate
To answer your direct question, you could use zip
to iterate the rest
and distance
together, but the issue is that the rest1
, rest2
, ... variables, which were doomed to fail. What happens when you copy/paste a bunch of lines, and forget to change one of them, accidentally writing rest.append(rest6); rest.append(rest6)
, forgetting rest7
?
Thirdly, your findDistance(from:long:)
function takes two Double
s (a latitude and a longitude), and uses them to construct a CLLocation
. But when you look where this function is used, you already have a CLLocation
, which you decompose into two Double
s, only for findDistance(from:long:)
to immediately reglue them back together into a CLLocation
. Instead, just make findDistance
take a CLLocation
directly.
Fourthly, the Restaurants
datatype is miss named. It's not a plurality of restaurants. It models a single restaurant. Name it accordingly: Restaurant
Applying these improvements (and also fixing a bunch of indenting along the way), we get:
struct Restaurant {
var name: String
var lat: Double
var long: Double
var distance: String?
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: nil),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: nil),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: nil,
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: nil),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: nil),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil),
]
var distances = [Double]()
for location in restaurants {
distance.append(findDistance(to: location))
}
func findDistance(to destination: CLLocation) -> Double {
let source = CLLocation(latitude: 31.461512, longitude: 74.272024)
let distanceInMeters = source.distance(from: destination)
return distanceInMeters
}
The direct problem
Here I'll illustrate the process I went through to address the direct question. However, don't use any of these. All these are shitty designs trying to work around underlying flaws in design. I show them in order to demonstrate what successive refinement looks like.
Now, to address the direct question:
First attempt: using zip(_:_:)
A first solution approach might be to use zip
to iterate both restaurants
and distances
in parallel, and then mutating each restaurant
from each `distance. Something like this:
for (restaurant, distance) in zip(restaurants, distances) {
restaurant.distance = distance
}
However, this won't work. Since Restaurant
is a value type, the restaurant
variable in the loop is a copy of that in the array. Setting its distance mutates the copy, but doesn't effect the original in the array.
Second attempt: manual indexing
We can work around this, albeit in a much less pretty way, by looping over the indices:
for i in restaurants.indices {
restaurants[i] = distances[i]
}
Third attempt: skip the distances
array.
The second attempt works, but if the only purpose of distances
is for us to fill it with a bunch of values, only to immediately use them and discard them, why do we both having the array at all?
for i in restaurants.indices {
restaurant.location = findDistance(to: location)
}
This is still not great, however. The Restaurant
data type suffers from two stage initialization, which is a code smell. First we initialize it with nil
location, then we set it to a real location. Why bother? Why don't we just set the location directly?
let distance = findDistance(to: location)
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: distance),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: distance),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: distance),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: distance),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: distance),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil),
]
But this is still not good design...
Fixing the flaws with findDistance(to:)
and Restaurant.distance
What does findDistance(to:)
even really do? Internally, it gets a distance from some hard-coded, unnamed location CLLocation(latitude: 31.461512, longitude: 74.272024)
. When I say someRestaurant.distance
, I would expect a distance to me. If it were a distance from some reference point A, I would expect the API to instead be spelt something like someResaurant.distanceFromNorthPole
, or something to that effect.
Furthermore, why is it the Restaurant
's job to store its distance to something else? What if I want restaurant.distanceToSouthPole
, restaurant.distanceToEquator
? The API would get pretty bloated, and a restaurant
type ends up doing way too much. And what if I restaurant.distanceToMe
? I can move, how is a pre-computed, stored value going to keep up with me as I move?
The solution is not to store a distance at all. Instead, provide an API that users of this data type can use to calculate distances to any point of their own choosing.
struct Restaurant {
var name: String
var lat: Double
var long: Double
func distance(from other: CLLocation) -> Double {
let selfLocation = CLLocation(latitude: self.lat, longitude: self.long)
return selfLocation.distance(from: other)
}
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
let pointA = CLLocation(latitude: 31.461512, longitude: 74.272024) // Name me!
// and now, whenever you need the distance to pointA:
for restaurant in restaurants {
let distanceFromA = restaurant.distance(from: pointA)
// This is for the purpose of a simple example only. Never hard code units like this,
// Use `Measurement` and `MeasurementFormatter` to create textual representations
// of measurements in the correct units, language and format for the user's locale.
print("\(restaurant.name) is \(distanceFromA) meters from \(pointA)")
}
And surprise, this is still not the best we can make this!
Don't store doubles for lats and longs
That's what CLLocation
is for. Notice that almost all uses of lat
and long
require first boxing it into a CLLocation
. So let's just store that directly, rather than separating individual components and passing them around independently. This prevents bugs like useLocation(lat: self.lat, long: self.long /* oops! */)
.
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
However, if you do this, the initializer now requires a CLLocation
instead of two separate lat
/long
Double
s. This is better for interacting with location APIs (where CLLocation
is the "common currency" type for exchanging location info), but it's more cumbersome for hard-coded locations like your restaurants, because all of your initializer calls get bloated with a bunch of calls to CLLocation.init(latitude:longitude:)
:
let restaurants = [
Restaurant(name: "English Tea House", CLLocation(latitude: 31.461812, longitude: 74.272524)),
Restaurant(name: "Cafe Barbera", CLLocation(latitude: 31.474536, longitude: 74.268103)),
Restaurant(name: "Butler's Chocolate", CLLocation(latitude: 31.467505), longitude: 74.251908)),
Restaurant(name: "Freddy's Cafe", CLLocation(latitude: 31.461312, longitude: 74.272124)),
Restaurant(name: "Arcadian Cafe", CLLocation(latitude: 31.464536, longitude: 74.268603)),
Restaurant(name: "Big Moes", CLLocation(latitude: 31.467305, longitude: 74.256908)),
]
To remedy this, we can tuck the CLLocation.init(latitude:longitude:)
away into a little initializer for convenience. I do so in an extension of Restaurant
rather than directly in the initial declaration of Restaurant
, because doing so preserves the compiler-generated initializer (called the "member-wise initializer"), which would otherwise be replaced:
extension Restaurant {
init(name: String, lat: Double, long: Double) {
self.init(name: name, location: CLLocation(latitude: lat, long))
}
}
Which allows us to regain the previous nice syntax:
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
Mutability
Restaurant names and locations are unlikely to change for the lifetime of an instance of your app, so there's no need to keep them mutable. So lets fix that:
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
And Finally...
We've come to the final stage. A well named Restaurant
that doesn't suffer from the need for two-stage initialization, that provides up-to-date distance data for any points a user might like, and that isn't vulnerable to copy paste errors thanks to lat
and long
being glued together into a CLLocation
.
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
extension Restaurant {
init(name: String, lat: Double, long: Double) {
self.init(name: name, location: CLLocation(latitude: lat, long))
}
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908,
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
let pointA = CLLocation(latitude: 31.461512, longitude: 74.272024) // Name me!
// and now, whenever you need the distance to pointA you can do this (for example):
for restaurant in restaurants {
let distanceFromA = restaurant.distance(from: pointA)
// This is for the purpose of a simple example only. Never hard code units like this,
// Use `Measurement` and `MeasurementFormatter` to create textual representations
// of measurements in the correct units, language and format for the user's locale.
print("\(restaurant.name) is \(distanceFromA) meters from \(pointA)")
}
How to find out distance between coordinates?
CLLocation has a distanceFromLocation method so given two CLLocations:
CLLocationDistance distanceInMeters = [location1 distanceFromLocation:location2];
or in Swift 4:
//: Playground - noun: a place where people can play
import CoreLocation
let coordinate₀ = CLLocation(latitude: 5.0, longitude: 5.0)
let coordinate₁ = CLLocation(latitude: 5.0, longitude: 3.0)
let distanceInMeters = coordinate₀.distance(from: coordinate₁) // result is in meters
you get here distance in meter so 1 miles = 1609 meter
if(distanceInMeters <= 1609)
{
// under 1 mile
}
else
{
// out of 1 mile
}
Function to calculate distance between two coordinates
What you're using is called the haversine formula, which calculates the distance between two points on a sphere as the crow flies. The Google Maps link you provided shows the distance as 2.2 km because it's not a straight line.
Wolfram Alpha is a great resource for doing geographic calculations, and also shows a distance of 1.652 km between these two points.
If you're looking for straight-line distance (as the crow files), your function is working correctly. If what you want is driving distance (or biking distance or public transportation distance or walking distance), you'll have to use a mapping API (Google or Bing being the most popular) to get the appropriate route, which will include the distance.
Incidentally, the Google Maps API provides a packaged method for spherical distance, in its google.maps.geometry.spherical
namespace (look for computeDistanceBetween
). It's probably better than rolling your own (for starters, it uses a more precise value for the Earth's radius).
For the picky among us, when I say "straight-line distance", I'm referring to a "straight line on a sphere", which is actually a curved line (i.e. the great-circle distance), of course.
Related Topics
Xcode Error While Validation - "Your Binary Is Not Optimized for iPhone 5"
How to Completely Hide the Status Bar in iOS Using Cordova
Open Links in Safari Instead of Uiwebview
Locationmanager:Didenterregion Not Called When a Beacon Is Detected
iOS Animate/Morph Shape from Circle to Square
When to Use a Colon with a @Selector
How to Position Views on Top of Each Other
Abpeoplepickernavigationcontroller Changes with iOS8
When Does Awakefromnib Get Called
How to Get Notified in Contact Changed Event in iOS
Uicontroleventtouchdragexit Triggers When 100 Pixels Away from Uibutton
How to Create a Uiimage from the Current Graphics Context
App Delegate Methods Aren't Being Called in iOS 13
Xcode 10B5 - Duplicate Symbol Linker Error, Can't Compile with Crashlytics
Path Extension and Mime Type of File in Swift
Thread Error in Ibaction While Overriding Prepareforsegue Function