I want to merge data of array into my struct variable
Below is the code
I want to add distance array values in rest -> distance
distance = ["12.44","45.32","56.1","54.22"]
merge this distance array into struct variable distance
var rest : [Restaurants] = []
var distance : [String] = []
struct Restaurants {
var name:String
var lat:Double
var long:Double
var distance:String?
}
let rest1 = Restaurants(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: nil)
let rest2 = Restaurants(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: nil)
let rest3 = Restaurants(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: nil)
let rest4 = Restaurants(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: nil)
let rest5 = Restaurants(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: nil)
let rest6 = Restaurants(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil)
rest.append(rest1)
rest.append(rest2)
rest.append(rest3)
rest.append(rest4)
rest.append(rest5)
rest.append(rest6)
for location in rest {
distance.append(findDistance(from: location.lat, long: location.long))
}
// I want to add distance array values in rest -> distance
func findDistance(from lat: Double, long: Double) -> Double {
let source = CLLocation(latitude: 31.461512, longitude: 74.272024)
let destination = CLLocation(latitude: lat, longitude: long)
let distanceInMeters = source.distance(from: destination)
return distanceInMeters
}
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 Doubles (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 Doubles, 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 Doubles. 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)")
}
If I assume your question correct, you want to fill in the distance property of each of the restaurants in rest with your distance variable. Also, assuming count of distance variable & rest variable are equal, you can do something like this,
if rest.count == distance.count {
(0..<rest.count).forEach {
rest[$0].distance = distance[$0]
}
}
I am using an API that returns a String that contains coordinate. How do I convert this response:
"route_pins":
"#30.0539983,30.943465#30.0539033,30.9434167#30.05379,30.9434467#30.0536117,30.943865#30.0535133,30.9439867#30.0534633,30.9440967#30.05353,30.94428#30.053675,30.944525#30.0539933,30.9449667#30.0541517,30.9452317#30.0542917,30.9454717#30.054365,30.9455717#30.0544667,30.945725#30.05471,30.9460733#30.0548667,30.94631#30.0550417,30.9465683#30.0553733,30.9471133#30.0557133,30.9476383#30.0558667,30.947905#30.0560083,30.9481767#30.0562517,30.94872#30.0564917,30.9492217#30.0565783,30.9494567#30.056645,30.9496883#30.0566167,30.9501883"
into an Array of CLLocationCoordinate2D to draw a line using MapKit using:
let polyLine = MKPolyline(coordinates: Locations , count: Locations.count)
busMapView.add(polyLine)
You can do it with a combination of components(separatedBy and map
Split the string by character # and drop the first (empty) item.
Map each item to CLLocationCoordinate2D by splitting the string by the comma, convert the strings to CLLocationCoordinate2D and create the coordinate.
let routePins = "#30.0539983,30.943465#30.0539033,30.9434167#30.05379,30.9434467#30.0536117,30.943865#30.0535133,30.9439867#30.0534633,30.9440967#30.05353,30.94428#30.053675,30.944525#30.0539933,30.9449667#30.0541517,30.9452317#30.0542917,30.9454717#30.054365,30.9455717#30.0544667,30.945725#30.05471,30.9460733#30.0548667,30.94631#30.0550417,30.9465683#30.0553733,30.9471133#30.0557133,30.9476383#30.0558667,30.947905#30.0560083,30.9481767#30.0562517,30.94872#30.0564917,30.9492217#30.0565783,30.9494567#30.056645,30.9496883#30.0566167,30.9501883"
let coordinates = routePins.components(separatedBy: "#").dropFirst().map { (pin) -> CLLocationCoordinate2D in
let latLng = pin.components(separatedBy: ",").map{ CLLocationDegrees($0)! }
return CLLocationCoordinate2D(latitude: latLng[0], longitude: latLng[1])
}
The result is [CLLocationCoordinate2D].
let dic:Dictionary<String,Any> = ["route_pins": "#30.0539983,30.943465#30.0539033,30.9434167#30.05379,30.9434467#30.0536117,30.943865#30.0535133,30.9439867#30.0534633,30.9440967#30.05353,30.94428#30.053675,30.944525#30.0539933,30.9449667#30.0541517,30.9452317#30.0542917,30.9454717#30.054365,30.9455717#30.0544667,30.945725#30.05471,30.9460733#30.0548667,30.94631#30.0550417,30.9465683#30.0553733,30.9471133#30.0557133,30.9476383#30.0558667,30.947905#30.0560083,30.9481767#30.0562517,30.94872#30.0564917,30.9492217#30.0565783,30.9494567#30.056645,30.9496883#30.0566167,30.9501883"]
let strLatLng = dic["route_pins"] as! String
let arrayOflatLng = strLatLng.components(separatedBy: "#")
var testcoords = [CLLocationCoordinate2D]()
for latLngStr in arrayOflatLng {
if let strLat = latLngStr.components(separatedBy: ",") as? [String], strLat.count == 2 {
testcoords.append(CLLocationCoordinate2D(latitude: CLLocationDegrees(strLat[0])!, longitude: CLLocationDegrees(strLat[1])!))
}
}
print("testcoords \(testcoords.count)")
let polyLine = MKPolyline(coordinates: testcoords , count: testcoords.count)
busMapView.add(polyLine)
The API string is parsed using 2 levels of parsing ,first we split the API string to locations strings , then using for loop to split each location to its coordinates and save coordinates to CLLocationCoordinate2D and append it to result array
let routePins = "#30.0539983,30.943465#30.0539033,30.9434167#30.05379,30.9434467#30.0536117,30.943865#30.0535133,30.9439867#30.0534633,30.9440967#30.05353,30.94428#30.053675,30.944525#30.0539933,30.9449667#30.0541517,30.9452317#30.0542917,30.9454717#30.054365,30.9455717#30.0544667,30.945725#30.05471,30.9460733#30.0548667,30.94631#30.0550417,30.9465683#30.0553733,30.9471133#30.0557133,30.9476383#30.0558667,30.947905#30.0560083,30.9481767#30.0562517,30.94872#30.0564917,30.9492217#30.0565783,30.9494567#30.056645,30.9496883#30.0566167,30.9501883"
let pins = routePins.components(separatedBy: "#")//first step is splitting the fetched array to pins array
var LocationsArray = [CLLocationCoordinate2D]()//this is the result array
for location in pins {
if(location.contains(",")){//checking that the location is containing lat ,long separtor
let coordinates = location.components(separatedBy: ",")//splitting the location to lat ,long
//safe parsing the string value to double vale for coordinates
let lat = Double(coordinates[0]) ?? 0.0
let long = Double( coordinates[1]) ?? 0.0
LocationsArray.append(CLLocationCoordinate2D(latitude: lat
,longitude: long))// appending new coordinates to locations array
}
}
How to detect which marker was pressed on a map. I have few markers on map and class Marker in markers array downloaded from API, that contains some data.
for example i have data like this
[States(name: "text1", long: 110.42400399999997,lat: -7.0343237999999992),
States(name: "text2", long: 110.42769829999997, lat: -7.0856947999999997),
States(name: "text3", long: 110.42922440000007, lat: -7.3250846999999997),
States(name: "text4", long: 117.11625830000003, lat: -0.50436380000000003),
States(name: "text5", long: 110.43093620000002, lat: -7.0730081999999994)]
if i tap on marker that contain data 1 (States(name: "text1", long: 110.42400399999997,lat: -7.0343237999999992))
How do i get index 0. and if i tap on marker that contain data 2, How do i get index 1?
So I suppose you added the markers by iterating through the array like this:
for state in states { // assuming states is the array you showed in the question
let marker = GMSMarker(position: CLLocationCoordinate2D(latitude: state.lat, longitude: state.long))
// configure the marker...
marker.map = mapView
}
The idea is that you add the markers to an array as well, just after you created it. Since you created the markers in the order of the data, each item in the array containing the markers corresponds to the data at the same index.
Let's declare the array of markers at class level:
var markers = [GMSMarker]()
Then in the for loop above, add the marker to markers:
markers.append(marker)
Now you can find out which datum is tapped just by:
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
if let index = markers.index(of: marker) {
let tappedState = states[index]
}
}