When I tried to use reverse geocoding,this error message showed up.
Geocode error: Error Domain=GEOErrorDomain Code=-3 "(null)"
My code is below:
import CoreLocation
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if let placemarks = placemarks {
reverseGeocodeLocations[hash] = placemarks
}
callback(placemarks, error)
}
This works only time to time, and I request reverseGeocode several times per seconds. So I guess this error message is related to the limit of request or something?
Is there any documentation about apple’s geocode request?
Thanks for advance.
Updated
here is my entire code for requesting
import CoreLocation
fileprivate struct ReverseGeocodeRequest {
private static let geocoder = CLGeocoder()
private static var reverseGeocodeLocations = [Int: [CLPlacemark]]()
private static let reverseGeocodeQueue = DispatchQueue(label: "ReverseGeocodeRequest.reverseGeocodeQueue")
private static var nextPriority: UInt = 0
fileprivate static func request(location: CLLocation, callback: #escaping ([CLPlacemark]?, Error?)->Void) {
let hash = location.hash
if let value = reverseGeocodeLocations[hash] {
callback(value, nil)
} else {
reverseGeocodeQueue.async {
guard let value = reverseGeocodeLocations[hash] else {
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if let placemarks = placemarks {
reverseGeocodeLocations[hash] = placemarks
}
callback(placemarks, error)
}
return
}
callback(value, nil)
}
}
}
let priority: UInt
let location: CLLocation
let handler : ([CLPlacemark]?, Error?)->Void
private init (location: CLLocation, handler: #escaping ([CLPlacemark]?, Error?)->Void) {
ReverseGeocodeRequest.nextPriority += 1
self.priority = ReverseGeocodeRequest.nextPriority
self.location = location
self.handler = handler
}
}
extension ReverseGeocodeRequest: Comparable {
static fileprivate func < (lhs: ReverseGeocodeRequest, rhs: ReverseGeocodeRequest) -> Bool {
return lhs.priority < rhs.priority
}
static fileprivate func == (lhs: ReverseGeocodeRequest, rhs: ReverseGeocodeRequest) -> Bool {
return lhs.priority == rhs.priority
}
}
extension CLLocation {
func reverseGeocodeLocation(callback: #escaping ([CLPlacemark]?, Error?)->Void) {
ReverseGeocodeRequest.request(location: self, callback: callback)
}
func getPlaceName(callback: #escaping (Error?, String?)->Void) {
self.reverseGeocodeLocation { (placemarks, error) in
guard let placemarks = placemarks, error == nil else {
callback(error, nil)
return
}
guard let placemark = placemarks.first else {
callback(nil, "Mysterious place")
return
}
if let areaOfInterest = placemark.areasOfInterest?.first {
callback(nil, areaOfInterest)
} else if let locality = placemark.locality {
callback(nil, locality)
} else {
callback(nil, "On the Earth")
}
}
}
}
After searching everywhere for the answer it was in Apples docs! :/
https://developer.apple.com/reference/corelocation/clgeocoder/1423621-reversegeocodelocation
Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. When the maximum rate is exceeded, the geocoder passes an error object with the value network to your completion handler.
When checking the error code in the completion handler it is indeed Network Error:2
Hope this helps someone!
Related
I am trying to get city weather in current location. I have model, where is getting weather data from JSON, and also I have model, where I am getting my location (latitude and longitude). But I don't know, how I can get this latitude and longitude in my link.
WeatherModel: where I use url link. I want use latitude and longitude from my LocationModel below
import Foundation
import CoreLocation
protocol IWeatherService {
func getCitiesWeather(forCoordinates coordinates: CLLocationCoordinate2D, completion: #escaping (Result<CitiesWeather, Error>) -> Void)
}
enum WeatherServiceError: Error {
case badUrl
}
final class WeatherService: IWeatherService {
func weatherURLString(forCoordinates coordinates: CLLocationCoordinate2D) -> String {
return "https://api.openweathermap.org/data/2.5/weather?lat=\(coordinates.latitude)&lon=\(coordinates.longitude)&units=metric&appid=b382e4a70dfb690b16b9381daac545ac&lang=ru"
}
func getCitiesWeather(forCoordinates coordinates: CLLocationCoordinate2D, completion: #escaping (Result<CitiesWeather, Error>) -> Void) {
//weatherURLString(forCoordinates: CLLocationCoordinate2D) ???
//LocationManager.shared.locationManager(CLLocationManager, didUpdateLocations: [CLLocation]) ???
//Проверка, что у нас есть url адрес
guard let url = URL(string: .url) else {
return completion(.failure(WeatherServiceError.badUrl))
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
do {
let result = try JSONDecoder().decode(CitiesWeather.self, from: data)
completion(.success(result))
}
catch {
print("failed to convert \(error)")
}
}
task.resume()
}
}
LocationModel:
import Foundation
import CoreLocation
protocol ILocationService {
func getUserLocation(completion: #escaping ((CLLocation) -> Void))
}
class LocationManager: NSObject, ILocationService, CLLocationManagerDelegate {
static let shared = LocationManager()
let manager = CLLocationManager()
public func getUserLocation(completion: #escaping ((CLLocation) -> Void)) {
self.completion = completion
manager.requestWhenInUseAuthorization()
manager.delegate = self
manager.startUpdatingLocation()
}
public func resolveLocationName(with location: CLLocation, completion: #escaping ((String?) -> Void)) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location, preferredLocale: .current) { placemarks, error in
guard let place = placemarks?.first, error == nil else {
completion(nil)
return
}
print(location)
var name = ""
if let locality = place.locality {
name += locality
}
if let adminRegion = place.administrativeArea {
name += ", \(adminRegion)"
}
completion(name)
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
completion?(location)
//Эти значения надо передать в WeatherService
print(location.coordinate.latitude)
print(location.coordinate.longitude)
manager.stopUpdatingLocation()
}
var completion: ((CLLocation) -> Void)?
}
As vadian said in his comment, this bit can't work:
private extension String {
static let url = "https://api.openweathermap.org/data/2.5/weather?lat=\(coordinates.latitude)&lon=\(coordinates.longitude)&units=metric&appid=b382e4a70dfb690b16b9381daac545ac&lang=ru"
}
You should create a function that takes coordinates and returns a URL string for a weather update:
func weatherURLString(forCoordinates coordinates: CLLocationCoordinate2D) {
return "https://api.openweathermap.org/data/2.5/weather?lat=\(coordinates.latitude)&lon=\(coordinates.longitude)&units=metric&appid=b382e4a70dfb690b16b9381daac545ac&lang=ru"
Rewrite your getCitiesWeather() function to take a parameter coordinates of type CLLocationCoordinate2D, and have it call the above function to generate the URL string it will use.
You have code that asks to update the user's location, and implements the locationManager(_:didUpdateLocations:) function. In your locationManager(_:didUpdateLocations:) function, you call a completion handler. Have that completion handler call the weather service with the user's updated location.
I am new iOS Developer
I want to change the websiteLogo API with a textfield to change the URL.
how can I change the line with the ***
with a var and a textfield in my viewcontroller?
With screenshoot it's will be easier to understand what I want? Thank you !!! Guys. OneDriveLink. 1drv.ms/u/s!AsBvdkER6lq7klAqQMW9jOWQkzfl?e=fyqOeN
private init() {}
**private static var pictureUrl = URL(string: "https://logo.clearbit.com/:http://www.rds.ca")!**
private var task: URLSessionDataTask?
func getQuote(callback: #escaping (Bool, imageLogo?) -> Void) {
let session = URLSession(configuration: .default)
task?.cancel()
task = session.dataTask(with: QuoteService.pictureUrl) { (data, response, error) in
DispatchQueue.main.async {
guard let data = data, error == nil else {
callback(false, nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
callback(false, nil)
return
}
let quote = imageLogo(image: data)
callback(true, quote)
print(data)
}
}
task?.resume()
}
First, please don't use screenshots do show your code. If you want help, others typically copy/paste your code to check whats wrong with it.
There are some minor issues with your code. Some hints from me:
Start your types with a big letter, like ImageLogo not imageLogo:
Avoid statics
Avoid singletons (they are almost statics)
Hand in the pictureUrl into getQuote
struct ImageLogo {
var image:Data
}
class QuoteService {
private var task: URLSessionDataTask?
func getQuote(from pictureUrl:URL, callback: #escaping (Bool, ImageLogo?) -> Void) {
let session = URLSession(configuration: .default)
task?.cancel()
task = session.dataTask(with: pictureUrl) {
(data, response, error) in
DispatchQueue.main.async {
guard let data = data, error == nil else {
callback(false, nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
callback(false, nil)
return
}
let quote = ImageLogo(image: data)
callback(true, quote)
print(data)
}
}
task?.resume()
}
}
Store an instance of QuoteService in your view controller
Call getQuote on that instance, handing in the pictureUrl
class ViewController : UIViewController {
var quoteService:QuoteService!
override func viewDidLoad() {
self.quoteService = QuoteService()
}
func toggleActivityIndicator(shown:Bool) { /* ... */ }
func update(quote:ImageLogo) { /* ... */ }
func presentAlert() { /* ... */ }
func updateconcept() {
guard let url = URL(string:textField.text!) else {
print ("invalid url")
return
}
toggleActivityIndicator(shown:true)
quoteService.getQuote(from:url) {
(success, quote) in
self.toggleActivityIndicator(shown:false)
if success, let quote = quote {
self.update(quote:quote)
} else {
self.presentAlert()
}
}
}
/* ... */
}
Hope it helps.
I think you want to pass textfield Text(URL Enter By user) in Web Services
Add a parameter url_str in getQuote function definition first and pass textfield value on that parameters
fun getQuote(url_str : String, callback : #escaping(Bool, ImgaeLogo/)->void){
}
I'm trying to do reverse geocoding for multiple locations at the same time. So I create a function performReverseGeoLocation. The problem is, that since CLGeocoder().reverseGeocodeLocation a closure, the completionHandlerLocations will get executed first. How do I change these functions so that the caller will get completion handler after all CLGeocoder().reverseGeocodeLocation inside the for loop is done?
Code I have tried:
private func getImageLocation() {
performReverseGeoLocation(completionHandlerLocations: { (cities, countries) in
print("***** This is executed before the reverse geo code location is done")
})
}
private func performReverseGeoLocation(completionHandlerLocations: #escaping (_ cities: [String], _ countries: [String]) -> Void) {
var cities = [String]()
var countries = [String]()
for image in self.images {
let longitude = image.longitude
let latitude = image.latitude
let location = CLLocation(latitude: latitude, longitude: longitude)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
print("***** This is executed after completionHandlerLocations is done")
if error != nil {
self.alertError("Reverse geocoder failed with error" + (error?.localizedDescription)!)
return
}
if placemarks!.count > 0 {
let pm = placemarks![0]
let country = pm.country
let city = pm.locality
if (!cities.contains(city!)) {
cities.append(city!)
}
if (!countries.contains(country!)) {
countries.append(country!)
}
}
else {
self.alertError("Fail to perform reverse geo location")
}
})
}
// THIS IS WILL EXECUTED FIRST
completionHandlerLocations(cities, countries)
}
You can do something like this:
var count = 0
for image in self.images {
...
CLGeocoder().reverseGeocodeLocation(location) {
// get result
counter ++
if count == self.images.count { // finish all requests
completionHandlerLocations(cities, countries)
}
}
}
That's the most simple way to do.
As i-am-jorf mentioned, you can create a DispatchGroup and wait for the notification when all reverse geocoding tasks are complete:
private func performReverseGeoLocation(completionHandlerLocations: #escaping (_ cities: [String], _ countries: [String]) -> Void) {
let group = DispatchGroup()
var cities = [String]()
var countries = [String]()
self.images.forEach { (location) in
group.enter()
let longitude = image.longitude
let latitude = image.latitude
let location = CLLocation(latitude: latitude, longitude: longitude)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemark, error) in
// do all your checks...
if placemark != nil && placemark!.count > 0 {
cities.append(placemark!.first!.locality!)
countries.append(placemark!.first!.country!)
}
group.leave()
})
}
group.notify(queue: DispatchQueue.main) {
completionHandlerLocations(cities, countries)
}
}
Check out this code:
func getReversedGeocodeLocation(location: CLLocation, completionHandler: #escaping ()->()) {
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if error != nil {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks != nil {
if placemarks!.count > 0 {
let pm = placemarks![0]
if let addressDictionary: [AnyHashable: Any] = pm.addressDictionary,
let addressDictionaryFormatted = addressDictionary["FormattedAddressLines"] {
let address = (addressDictionaryFormatted as AnyObject).componentsJoined(by: ", ")
self.addressInViewController = address
}
completionHandler()
}
} else {
print("Problem with the data received from geocoder")
}
})
}
In the viewController
override func viewDidLoad() {
var addressInViewController = String()
getReversedGeocodeLocation(location: location, completionHandler: {
print("After geo finished")
})
}
This is a simple case for using closures. As you can see, when the reverse geo finishes, it updates the addressInViewController variable which is defined outside the function itself. I'm a bit confused when it comes to closures but I do know it's essentially passing in another function as a parameter, into a function. So can I pass in something like (_ String: x)->() instead of ()->() where the address variable would be populated from the main reverse geo function and passed along? I tried doing that but it says "x" is undefined. If this is achieve-able then I guess I can decouple my code in a better way using closures.
Thanks and have a great day :)
define your methods like this
func getReversedGeocodeLocation(location: CLLocation, completionHandler: #escaping (_ value : Any)->()) {
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if error != nil {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks != nil {
if placemarks!.count > 0 {
let pm = placemarks![0]
if let addressDictionary: [AnyHashable: Any] = pm.addressDictionary,
let addressDictionaryFormatted = addressDictionary["FormattedAddressLines"] {
let address = (addressDictionaryFormatted as AnyObject).componentsJoined(by: ", ")
self.addressInViewController = address
}
completionHandler(address)
}
} else {
print("Problem with the data received from geocoder")
}
})
}
override func viewDidLoad() {
var addressInViewController = String()
getReversedGeocodeLocation(location: location, completionHandler: { (_ values : Any) in
self. addressInViewController = values
})
}
Make dataType of Value according to your need.
I am using a Master Detail Application. Master Screen is a Dashboard and on selecting an item, moves to the detailed screen where I trigger an Alamofire request in the backend
Below is the snippet
class APIManager: NSObject {
class var sharedManager: APIManager {
return _sharedManager
}
private var requests = [Request]()
// Cancel any ongoing download
func cancelRequests() {
if requests.count > 0 {
for request in requests {
request.cancel()
}
}
}
func getData(completion: (dataSet: [Data]?, error: NSError?) -> Void) {
let request = Alamofire.request(.GET, "http://request")
.response { (request, response, data, error) in
dispatch_async(dispatch_get_main_queue(), {
if(error == nil) {
if let response = data, data = (try? NSJSONSerialization.JSONObjectWithData(response, options: [])) as? [NSDictionary] {
var dataSet = [Data]()
for (_, dictionary) in data.enumerate() {
let lat = dictionary["Latitude"]
let lng = dictionary["Longitude"]
let id = dictionary["ID"] as! Int
let data = Data(lat: lat!, long: lng!, id: shuttleID)
dataSet.append(data)
}
completion(dataSet: dataSet, error: nil)
}
} else { completion(dataSet: nil, error: error) }
})
}
requests.append(request)
}
}
I have a singleton API manager class and from the detail view controller I call getData() function. Everything works fine.
But, when I push and pop repeatedly, I see rapid increase in the memory and after 10-15 attempts, I get memory warning. However in the AppDelegate I am managing it to show an Alert message and adding a delay timer for 8 seconds. But however after 20-25 attempts app crashes due to memory warning.
In viewWillDisappear(), I cancel any ongoing requests also. But I couldn't able to stop memory warning issue. I commented the part where I call the request, I see no issues, even memory consumption is less.
I welcome ideas.
The problem is you are never removing the requests that you append to the member variable 'requests'.
You will need to ensure to remove the request when you either cancel it or when the request completes successfully.
Do the following modifications-
func cancelRequests() {
if requests.count > 0 {
for request in requests {
request.cancel()
}
}
requests.removeAll() //Delete all canseled requests
}
also
func getData(completion: (dataSet: [Data]?, error: NSError?) -> Void) {
let request = Alamofire.request(.GET, "http://request")
.response { (request, response, data, error) in
dispatch_async(dispatch_get_main_queue(), {
if(error == nil) {
if let response = data, data = (try? NSJSONSerialization.JSONObjectWithData(response, options: [])) as? [NSDictionary] {
var dataSet = [Data]()
for (_, dictionary) in data.enumerate() {
let lat = dictionary["Latitude"]
let lng = dictionary["Longitude"]
let id = dictionary["ID"] as! Int
let data = Data(lat: lat!, long: lng!, id: shuttleID)
dataSet.append(data)
}
requests.removeObject(request)
completion(dataSet: dataSet, error: nil)
}
} else {
requests.removeObject(request)
completion(dataSet: nil, error: error) }
})
}
requests.append(request)
}
Add this Handy extension on Array to remove item to your code:
// Swift 2 Array Extension
extension Array where Element: Equatable {
mutating func removeObject(object: Element) {
if let index = self.indexOf(object) {
self.removeAtIndex(index)
}
}
mutating func removeObjectsInArray(array: [Element]) {
for object in array {
self.removeObject(object)
}
}
}
On analysis, I found that the memory warning was not due to the Alamofire request. It was due to MKMapView. Loading a MKMapView, zooming in and zooming out consumes more memory. So, in viewWillDisappear I did the fix.
override func viewWillDisappear(animated:Bool){
super.viewWillDisappear(animated)
self.applyMapViewMemoryFix()
}
func applyMapViewMemoryFix(){
switch (self.mapView.mapType) {
case MKMapType.Hybrid:
self.mapView.mapType = MKMapType.Standard
break;
case MKMapType.Standard:
self.mapView.mapType = MKMapType.Hybrid
break;
default:
break;
}
self.mapView.showsUserLocation = false
self.mapView.delegate = nil
self.mapView.removeFromSuperview()
self.mapView = nil
}
Courtesy - Stop iOS 7 MKMapView from leaking memory