When I use this method, for update annotation title,i always have error when I move map to another city or country:
Error is:
The operation couldn’t be completed. (kCLErrorDomain error 2.)
Yes, I have seen similar posts on this topic. However, there are no solutions to the problem, and yes, I checked the Internet connection, and tried to test on the device.
Method is:
and second problem, my "addressString" always empty, also when I have placemark, and "city"/"state" values printed, but addressString always empty.
But latitude and longitude always print, when I move map.
func convertLatLongToAddress(latitude: Double,longitude: Double) -> String {
var addressString: String = ""
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: latitude, longitude: longitude)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
if let error = error {
debugPrint("ERROR IS: \(error.localizedDescription)")
}
// Place details
var placeMark: CLPlacemark?
placeMark = placemarks?[0]
// City
if let city = placeMark?.locality {
print(city)
addressString = city
}
// State
if let state = placeMark?.administrativeArea {
print(state)
addressString += ", \(state)"
}
})
debugPrint("LATITUDE: \(latitude)")
debugPrint("LONGITUDE: \(longitude)")
return addressString
}
Where I use this method:
Map(coordinateRegion: $manager.region,
annotationItems: manager.annotationItems
) { place in
MapAnnotation(coordinate: manager.region.center) {
PinView(text: "\(manager.convertLatLongToAddress(latitude: manager.region.center.latitude, longitude: manager.region.center.longitude))")
.offset(x: 0, y: -15)
}
}
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .top)
.cornerRadius(8)
Related
I am having trouble with working over data I receive from server.
Each time server sends data it is new coordinates for each user. I am looping over each incoming data, and I want to send the data in completion to receive them on other end. And update model class with them. At the moment I have two users in server. And sometimes the closure passes data two times, but sometimes just one. And interesting thing is that class properties are not updated, at least I dont see them on UI.
This is function I call when data is received. Response is just string I split to get user data.
func updateUserAdrress(response: String, completion: #escaping (Int, Double, Double, String) -> Void){
var data = response.components(separatedBy: "\n")
data.removeLast()
data.forEach { part in
let components = part.components(separatedBy: ",")
let userID = components[0].components(separatedBy: " ")
let id = Int(userID[1])
let latitude = Double(components[1])!
let longitude = Double(components[2])!
let location = CLLocation(latitude: latitude, longitude: longitude)
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if error == nil {
let placemark = placemarks?[0]
if let thoroughfare = placemark?.thoroughfare, let subThoroughfare = placemark?.subThoroughfare {
let collectedAddress = thoroughfare + " " + subThoroughfare
DispatchQueue.main.async {
completion(id!, latitude, longitude, collectedAddress)
}
}
} else {
print("Could not get address \(error!.localizedDescription)")
}
}
}
}
In this function I try to invoke the changes on objects. As the incoming data from server is different the first time I have splited the functionality, so the correct block of code would be called.
func collectUsers(_ response: String){
if users.count != 0{
updateUserAdrress(response: response) { id, latitude, longitude, address in
if let index = self.users.firstIndex(where: { $0.id == id }){
let user = self.users[index]
user.latitude = latitude
user.longitude = longitude
user.address = address
}
}
}else{
var userData = response.components(separatedBy: ";")
userData.removeLast()
let users = userData.compactMap { userString -> User? in
let userProperties = userString.components(separatedBy: ",")
var idPart = userProperties[0].components(separatedBy: " ")
if idPart.count == 2{
idPart.removeFirst()
}
guard userProperties.count == 5 else { return nil }
guard let id = Int(idPart[0]),
let latitude = Double(userProperties[3]),
let longitude = Double(userProperties[4]) else { return nil }
let collectedUser = User(id: id, name: userProperties[1], image: userProperties[2], latitude: latitude, longitude: longitude)
return collectedUser
}
DispatchQueue.main.async {
self.users = users
}
}
}
As I also need user address when app starts in model I have made simular function to call in init so it would get address for user. That seems to be working fine. But for more context I will add the model to.
class User: Identifiable {
var id: Int
let name: String
let image: String
var latitude: Double
var longitude: Double
var address: String = ""
init(id: Int, name: String, image: String, latitude: Double, longitude: Double){
self.id = id
self.name = name
self.image = image
self.latitude = latitude
self.longitude = longitude
getAddress(latitude: latitude, longitude: longitude) { address in
self.address = address
}
}
func getAddress(latitude: Double, longitude: Double, completion: #escaping (String) -> Void){
let location = CLLocation(latitude: latitude, longitude: longitude)
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if error == nil {
let placemark = placemarks?[0]
if let thoroughfare = placemark?.thoroughfare, let subThoroughfare = placemark?.subThoroughfare {
let collectedAddress = thoroughfare + " " + subThoroughfare
completion(collectedAddress)
}
} else {
print("Could not get address \(error!.localizedDescription)")
}
}
}
}
And one interesting thing. That when closure receives two times data and I assign them to class, there are no changes on UI.
I made this project on Xcode 13.4.1 because on Xcode 14 there is a bug on MapAnnotations throwing purple warnings on view changes.
I am trying to build a small Map app where location for user changes all the time. In general I get latitude and longitude updates all the time. And I need to display them and show the change with sliding animation, simular to Apple FindMyFriend, when it slides over map when they are moving in live.
This is my view:
struct ContentView: View {
#StateObject var request = Calls()
#State private var mapRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 56.946285, longitude: 24.105078), span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02))
var body: some View {
Map(coordinateRegion: $mapRegion, annotationItems: $request.users){ $user in
withAnimation(.linear(duration: 2.0)) {
MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: user.latitude, longitude: user.longitude)){
Circle()
}
}
}
}
}
And function call in view model, whitch changes user location, the response is just incoming string from API:
func collectUsers(_ response: String){
if users.count != 0{
var data = response.components(separatedBy: "\n")
data.removeLast()
let updates = self.users.map{ user -> User in
let newData = updateUserLocation(user: user, input: data)
return User(id: user.id, name: user.name, image: user.image, latitude: Double(newData[1])!, longitude: Double(newData[2])!)
}
DispatchQueue.main.async {
self.users = updates
}
}else{
var userData = response.components(separatedBy: ";")
userData.removeLast()
let users = userData.compactMap { userString -> User? in
let userProperties = userString.components(separatedBy: ",")
var idPart = userProperties[0].components(separatedBy: " ")
if idPart.count == 2{
idPart.removeFirst()
}
guard userProperties.count == 5 else { return nil }
guard let id = Int(idPart[0]),
let latitude = Double(userProperties[3]),
let longitude = Double(userProperties[4]) else { return nil }
return User(id: id, name: userProperties[1], image: userProperties[2], latitude: latitude, longitude: longitude)
}
DispatchQueue.main.async {
self.users = users
}
}
}
And ofcourse my #Published:
class Calls: ObservableObject{
#Published var users = [User]()
When I use the MapMarker instead of MapAnnotation the error does not appier. I would use marker, but I need each user view in map to be different.
If any one stumbles with the same issue. I spent entire day to solve this, but the awnser is that in Xcode 14 it is a bug. After I installer Xcode 13.4.1 error messages disappiered.
I'm trying to play with SwiftUI and make a map with dropped pins from locations (generated from a database API).
I have my struct:
struct Locations: Decodable, Identifiable {
var id: Int { _id }
let _id: Int // the one used in the database
let streetaddress: String?
let suburb: String?
let state: String?
let postcode: String?
// get the co-ordinates now
var coordinates: CLLocationCoordinate2D? {
let geocoder = CLGeocoder()
var output = CLLocationCoordinate2D()
if let address = streetaddress,
let suburb = suburb,
let postcode = postcode,
let state = state {
let fullAddress = "\(address) \(suburb), \(state) \(postcode)"
geocoder.geocodeAddressString( String(fullAddress) ) { ( placemark, error ) in
if let latitude = placemark?.first?.location?.coordinate.latitude,
let longitude = placemark?.first?.location?.coordinate.longitude {
output = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
}
}
return output
}
}
However, whenever I call the coordinates I am getting a {"msg":"#NullIsland Received a latitude or longitude from getLocationForBundleID that was exactly zero", "latIsZero":0, "lonIsZero":0} error.
I have added the error snippet from here: https://stackoverflow.com/a/65837163/1086990 to dive deeper into the error, and it is returning network: network was unavailable or a network error occurred.
I am able to call Map() in the view, and permissions are set in the info.plist as well as see my current location, etc.
Is there something I'm missing or is it not calculating because the Strings are all optional? Been trying to understand how it's not generating the coordinates from the address.
If I try to debug in the view I tried this:
struct MapView: View {
var body: some View {
Text("Hello World")
.onAppear {
for l in modelData.locations {
print( String("\(l.coordinates)") )
}
}
}
}
// console
// Optional(__C.CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0))
// ...
// Optional(__C.CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0))
The issue there is that geocodeAddressString is an asynchronous method. You are returning the value before receiving the result. What you need is a method instead of a computed property and a completion handler.
func coordinate(completion: #escaping (CLLocationCoordinate2D?, Error?) -> Void) {
let streetAddress = streetAddress ?? ""
let suburb = suburb ?? ""
let postCode = postCode ?? ""
let state = state ?? ""
let fullAddress = "\(streetAddress) \(suburb), \(state) \(postCode)"
CLGeocoder().geocodeAddressString(fullAddress) { completion($0?.first?.location?.coordinate, $1) }
}
Usage:
#State var location = Location(id: 1, streetAddress: "One Infinite Loop", suburb: "Cupertino", state: "CA", postCode: "95014")
var body: some View {
Text("Hello, world!")
.padding()
.onAppear {
location.coordinate { coordinate, error in
guard let coordinate = coordinate else {
print("error:", error ?? "nil")
return
}
print("coordinate", coordinate)
}
}
}
This will print
coordinate CLLocationCoordinate2D(latitude: 37.331656, longitude: -122.0301426)
as mentioned geocodeAddressString is an asynchronous function. That means you have to use some completion handler. So you cannot use it like you do with coordinates.
Here is some very basic code using a function instead:
struct Locations: Decodable, Identifiable {
var id: String = UUID().uuidString
// let _id: Int // the one used in the database
let streetaddress: String? = "1 Infinite Loop"
let suburb: String? = "Cupertino"
let state: String? = "CA"
let postcode: String? = ""
func getCoordinates(handler: #escaping ((CLLocationCoordinate2D) -> Void)) {
if let address = streetaddress, let suburb = suburb, let postcode = postcode, let state = state {
let fullAddress = "\(address) \(suburb), \(state) \(postcode)"
CLGeocoder().geocodeAddressString(fullAddress) { ( placemark, error ) in
handler(placemark?.first?.location?.coordinate ?? CLLocationCoordinate2D())
}
}
}
}
struct ContentView: View {
let locs = Locations()
#State var coordString = ""
var body: some View {
Text(locs.streetaddress ?? "no address")
Text(coordString)
.onAppear {
locs.getCoordinates() { coords in
coordString = "\(coords.latitude), \(coords.longitude)"
}
}
}
}
I am working in Swift trying to update an organization struct that will need to hold a latitude and longitude. I created a mutating function in the struct that will update the latitude and longitude based on the organization organization struct’s address. I got it to work, but the issue is that when I call the mutating function, I need to manually enter the variable name with the .latitude and .longitude. Is there a way that I can pass the variable struct’s name automatically and reference the .latitude and .longitude without calling the specific variable name with it so I can make it more usable? I included an example below with my code. Thanks for your help!
import UIKit
import PlaygroundSupport
import CoreLocation
PlaygroundPage.current.needsIndefiniteExecution = true
struct organization {
var name: String
var address: String
var latitude: CLLocationDegrees = 0 //default setting for latitude
var longitude: CLLocationDegrees = 0 //default setting for longitude
mutating func getCoordinateFrom(completion: #escaping(_ coordinate: CLLocationCoordinate2D?, _ error: Error?) -> () ) {
CLGeocoder().geocodeAddressString(address) { placemarks, error in
completion(placemarks?.first?.location?.coordinate, error)
}
}
}
struct Coordinates {
var latitude: Double
var longitude: Double
}
//create an wildernessLodge variable of type organization
var wildernessLodge = organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830")
wildernessLodge.getCoordinateFrom { coordinate, error in
guard let coordinate = coordinate, error == nil else { return }
wildernessLodge.latitude = coordinate.latitude
wildernessLodge.longitude = coordinate.longitude
print("update 1 \(wildernessLodge)")
}
I'm a bit confused by your code. Why is getCoordinateFrom marked as mutating? Perhaps you meant to write something like this.
mutating func getCoordinatesFromAddress() {
CLGeocoder().geocodeAddressString(address) { placemarks, error in
guard let coordinate = placemarks?.first?.location?.coordinate, error == nil else { return }
self.latitude = coordinate.latitude
self.longitude = coordinate.longitude
}
}
Now this function is mutating (it modifies self), and
wildernessLodge.getCoordinateFrom { coordinate, error in ... }
can be replaced with
wildernessLodge.getCoordinatesFromAddress()
The only reason to leave the getCoordinateFrom method is if, somewhere in your code, you intend to get coordinates from an address but not update the coordinates in the struct. I can't imagine a good reason to do that, so I would recommend replacing the getCoordinateFrom method with something else.
Alternatively, if you generally intend to set the coordinates right after creating a value of this type, you might want to consider something like this.
init(name: String, address: String) {
self.name = name
self.address = address
CLGeocoder().geocodeAddressString(address) { placemarks, error in
guard let coordinate = placemarks?.first?.location?.coordinate, error == nil else { return }
self.latitude = coordinate.latitude
self.longitude = coordinate.longitude
}
}
or
init(name: String, address: String) {
self.name = name
self.address = address
self.getCoordinatesFromAddress()
}
Then, you could create an organization using organization(name: name, address: address) and the coordinates would automatically be set correctly.
If neither of these are satisfactory, maybe you should create two different structs to capture the behavior you want.
struct Organization {
var name: String
var address: String
func withCoordinates(completion: #escaping(_ coordinate: CLLocationCoordinate2D?, _ error: Error?) -> () ) {
CLGeocoder().geocodeAddressString(address) { placemarks, error in
completion(placemarks?.first?.location?.coordinate, error)
}
}
}
struct OrganizationWithCoordinates {
var name: String
var address: String
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
init(from organization: Organization) {
self.name = organization.name
self.address = organization.address
organization.withCoordinates { coordinate, error in
guard let coordinate = coordinate, error == nil else { return }
self.latitude = coordinate.latitude
self.longitude = coordinate.longitude
}
}
}
I would prefer an approach like this, but I like having lots of types.
Finally, as noted in the comments, if you are really just concerned with brevity, you can replace
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
with
var coordinates: CLLocationCoordinate2D
var latitude: CLLocationDegrees { coordinates.latitude }
var longitude: CLLocationDegrees { coordinates.longitude }
and then replace
wildernessLodge.latitude = coordinate.latitude
wildernessLodge.longitude = coordinate.longitude
with
wildernessLodge.coordinates = coordinate
In fact, you should feel free to combine any of these approaches.
Edit: As pointed out, these solutions do not work as-is. The fundamental tension is trying to work with CLGeocoder's async method synchronously. One solution is to use a class instead of a struct. The other approach is to use a modification of the withCoordinate method above:
struct Organization {
var name: String
var address: String
func withCoordinate(callback: #escaping (OrganizationWithCoordinate?, Error?) -> Void) {
CLGeocoder().geocodeAddressString(self.address) { placemarks, error in
if let coordinate = placemarks?.first?.location?.coordinate, error == nil {
let orgWithCoord = OrganizationWithCoordinate(name: self.name, address: self.address, latitude: coordinate.latitude, longitude: coordinate.latitude)
callback(orgWithCoord, nil)
} else {
callback(nil, error)
}
}
}
}
struct OrganizationWithCoordinate {
var name: String
var address: String
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
}
Organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830").withCoordinate { orgWithCoord, error in
guard let orgWithCoord = orgWithCoord, error == nil else {
print("Error")
return
}
print(orgWithCoord)
}
This embraces the async nature of CLGeocoder.
Another solution could be to force CLGeocoder to be synchronous using DispatchSemaphore as follows. I don't think these work correctly in Playgrounds, but this should work in an actual app.
struct Organization {
var name: String
var address: String
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
init(name: String, address: String) throws {
self.name = name
self.address = address
var tempCoordinate: CLLocationCoordinate2D?
var tempError: Error?
let sema = DispatchSemaphore(value: 0)
CLGeocoder().geocodeAddressString(address) { placemarks, error in
tempCoordinate = placemarks?.first?.location?.coordinate
tempError = error
sema.signal()
}
// Warning: Will lock if called on DispatchQueue.main
sema.wait()
if let error = tempError {
throw error
}
guard let coordinate = tempCoordinate else {
throw NSError(domain: "Replace me", code: -1, userInfo: nil)
}
self.longitude = coordinate.longitude
self.latitude = coordinate.latitude
}
}
// Somewhere in your app
let queue = DispatchQueue(label: "Some queue")
queue.async {
let wildernessLodge = try! Organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830")
DispatchQueue.main.async {
print(wildernessLodge)
}
}
Here, a new queue to do Organization related work is created to avoid locking up the main queue. This method creates the least clunky-looking code in my opinion, but probably is not the most performant option. The location APIs are async for a reason.
I am trying to make a fetch for places in a certain, small bounds that match a filter with no string search doing the following:
let filter = GMSAutocompleteFilter()
filter.type = .establishment
let startCoord = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let upperCoord = startCoord.translate(using: -7, longitudinalMeters: 7)
let lowerCoord = startCoord.translate(using: 7, longitudinalMeters: -7)
let bounds = GMSCoordinateBounds(coordinate: upperCoord, coordinate: lowerCoord)
GMSPlacesClient.shared().autocompleteQuery(
"",
bounds: bounds,
filter: filter,
callback: {
(results, error) in
if error != nil {
self.proxyView!.errorLbl.displayMessage(FAUErrorLabel.MessageLevel.error, message: RQTText.errorsAPIFail)
}
else{
print(results)
print("done")
}
}
)
However, if the string query is empty, the request fails. How can I search places with the ios SDK without a search string?