cellForRowAtIndexPath being called very late - ios

I am trying to fetch JSON data from OpenWeatherMap API depending on user location and display it in a tableview.
First, I just init my table view :
init(_ coder: NSCoder? = nil) {
self.tableView = UITableView()
self.locationManager = CLLocationManager()
}
Then in viewDidLoad I call a function launchLocationOperations() to get user's location :
func launchLocationOperations() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
In my delegate :
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.locationManager.stopUpdatingLocation()
let locationArray = locations as NSArray
let currentLocation = locationArray.lastObject as! CLLocation
if self.currentCoordinates == nil ||
(self.currentCoordinates?.latitude != currentLocation.coordinate.latitude
||
self.currentCoordinates?.longitude != currentLocation.coordinate.longitude) {
self.currentCoordinates = currentLocation.coordinate
self.hasLoadedCoordinates = true
self.fetchWeatherInformations()
}
}
Then calling fetchWeatherInformation() :
func fetchWeatherInformations() {
// I build my path
let urlPath = StringUtils.buildUrl(self.currentCoordinates)
guard let url = URL(string: urlPath) else { return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, _, error in
do {
let jsonResponse = try data?.toJSON() as? [String : Any]
self.openWeatherMapResponse = OpenWeatherMapResponse.convert(jsonResponse: jsonResponse)
self.displayUI()
} catch {
print("Error while fetching JSON response : \(error.localizedDescription)")
}
}.resume()
}
And in displayUI() :
func displayUI() {
tableView.delegate = self
tableView.dataSource = self
}
So I have two problems here :
First, didUpdateLocations is being called many times even if I ask to stopUpdatingLocation() when entering the function.
Second, cellForRowAtIndexPath is being called very, very late, like 5 seconds late. I don't know why it is happening because the other dataSource/delegate methods are called instantly after entering displayUI()...
Thanks for your help.

Related

swift MapKit not showing annotation pins

I'm learning swift and trying to use SwiftyJson to parse a json file and add annotations in the map view but couldn't get the pins showed on the simulator. I have a warning in the debugging area says that Could not inset legal attribution from corner 4. My code is as below and I've checked some of the answers about this problem but still couldn't fix it. Any help is greatly appreciated.
class StationsViewController: UIViewController {
var stations = [Station]()
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self //as MKMapViewDelegate
//mapView.showsUserLocation = YES
fetchJsonData()
mapView.addAnnotations(stations)
}
func fetchJsonData() {
// Fetching client list.
let api_json_url = URL(string:"https://feeds.divvybikes.com/stations/stations.json")
// Create a URL request with the API address
let urlRequest = URLRequest(url: api_json_url!)
// Submit a request to get the JSON data
//let session = URLSession.shared
let task = URLSession.shared.dataTask(with: urlRequest) {data,response,error in
// if there is an error, print the error and do not continue
if error != nil {
print("Failed to parse")
return
}
// if there is no error, fetch the json formatted content
else{
let json = JSON(data:data!)
if let stationJSONs = json["stationBeanList"].array {
for stationJSON in stationJSONs {
if let station = Station.from(json: stationJSON) {
self.stations.append(station)
}
}
}
}// end if
} // end getDataSession
task.resume()
} // end readJsonData function
}
for stationJSON in stationJSONs {
if let station = Station.from(json: stationJSON) {
self.stations.append(station)
let latitude = station["latitude"]
let longitude = station["longitude"]
let annotation = MKPointAnnotation()
let centerCoordinate = CLLocationCoordinate2D(latitude: latitude, longitude)
annotation.coordinate = centerCoordinate
annotation.title = "Pass Title here"
mapView.addAnnotation(annotation)
}
}
check this.
You need to call addAnnotation in fetchJsonData(), because fetchJsonData() is executed asynchronously.
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self //as MKMapViewDelegate
//mapView.showsUserLocation = YES
fetchJsonData()
}
func fetchJsonData() {
// Fetching client list.
let api_json_url = URL(string:"https://feeds.divvybikes.com/stations/stations.json")
// Create a URL request with the API address
let urlRequest = URLRequest(url: api_json_url!)
// Submit a request to get the JSON data
//let session = URLSession.shared
let task = URLSession.shared.dataTask(with: urlRequest) {data,response,error in
// if there is an error, print the error and do not continue
if error != nil {
print("Failed to parse")
return
}
// if there is no error, fetch the json formatted content
else{
let json = JSON(data:data!)
if let stationJSONs = json["stationBeanList"].array {
for stationJSON in stationJSONs {
if let station = Station.from(json: stationJSON) {
self.stations.append(station)
mapView.addAnnotation(station)
}
}
}
}// end if
} // end getDataSession
task.resume()
} // end readJsonData function

iOS location services in the background to occur at smaller intervals or distances

the following code is my app delegate file, that I updated for swift 3.0.2
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var locationManager: CLLocationManager?
var significatLocationManager : CLLocationManager?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if(UIApplication.shared.backgroundRefreshStatus == UIBackgroundRefreshStatus.available){
print("yes")
}else{
print("no")
}
if launchOptions != nil{
let remoteNotif = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? NSDictionary
if remoteNotif != nil {
self.significatLocationManager = CLLocationManager()
self.significatLocationManager?.delegate = self
self.significatLocationManager?.requestAlwaysAuthorization()
if #available(iOS 9.0, *) {
self.significatLocationManager!.allowsBackgroundLocationUpdates = true
}
self.significatLocationManager?.startMonitoringSignificantLocationChanges()
}else{
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
self.locationManager?.requestAlwaysAuthorization()
if #available(iOS 9.0, *) {
self.locationManager!.allowsBackgroundLocationUpdates = true
}
self.locationManager?.startMonitoringSignificantLocationChanges()
}
}else{
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
self.locationManager?.requestAlwaysAuthorization()
if #available(iOS 9.0, *) {
self.locationManager!.allowsBackgroundLocationUpdates = true
}
self.locationManager?.startMonitoringSignificantLocationChanges()
}
return true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
//let locationArray = locations as NSArray
//let locationObj = locationArray.lastObject as! CLLocation
//let coord = locationObj.coordinate
let currentLocation = locations.last!
//print("Current location: \(currentLocation)")
print("delegate longitude: \(currentLocation.coordinate.longitude)")
print("delegate latitude: \(currentLocation.coordinate.latitude)")
let t_longtitude = String(currentLocation.coordinate.longitude)
let t_latitude = String(currentLocation.coordinate.latitude)
let longtitude:Double = currentLocation.coordinate.longitude as Double
let latitude:Double = currentLocation.coordinate.latitude as Double
let svd_longtitude:Double = (UserDefaults.standard.object(forKey: "longtitude") as? Double)!
let svd_latitude:Double = (UserDefaults.standard.object(forKey: "latitude") as? Double)!
let coordinate1 = CLLocation(latitude: latitude, longitude: longtitude)
let coordinate2 = CLLocation(latitude: svd_latitude, longitude: svd_longtitude)
let distanceInMeters = coordinate1.distance(from: coordinate2) // result is in meters
print("distanceInMeters: \(distanceInMeters)")
UserDefaults.standard.set(latitude, forKey: "latitude")
UserDefaults.standard.set(longtitude, forKey: "longtitude")
UserDefaults.standard.synchronize()
let device_id = UIDevice.current.identifierForVendor!.uuidString
let status = update_device(longtitude: t_longtitude, latitude: t_latitude, device_id: device_id)
print("status: \(status)")
}
func applicationDidEnterBackground(_ application: UIApplication) {
if self.significatLocationManager != nil {
self.significatLocationManager?.startMonitoringSignificantLocationChanges()
}else{
self.locationManager?.startMonitoringSignificantLocationChanges()
}
}
func update_device(longtitude: String, latitude:String, device_id:String) -> Int {
print("update device")
let deviceID = UIDevice.current.identifierForVendor!.uuidString
var status:Int = 0
var request = URLRequest(url: URL(string: "https://someurl")!)
request.httpMethod = "POST"
let postString = "device_id=\(deviceID)&longtitude=\(longtitude)&latitude=\(latitude)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
let responseString:String = (String(data: data, encoding: .utf8))!
print("responseString = \(String(describing: responseString))")
let dict = self.convertToDictionary(text: responseString)
//UserDefaults.standard.set(dict, forKey: "myUserDetails")
//UserDefaults.standard.synchronize()
//self.activityIndicator.stopAnimating()
//let status:Int = dict?["status"] as! Int
print("status: \(status)")
status = dict?["status"] as! Int
}
task.resume()
return status
}
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
}
I have asked for allow always, updated info.plist with
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to your location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to your location</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
The code works and my db is getting filled, but when app is in the background didUpdateLocations is triggered to far apart from location to location. What can I do to make app call didUpdateLocations when change between former location and new location is less then 200 meters
thanks
You can set location for 100 meteres like this, it will update you if location is changed to 100 meteres from current location
_locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;

For loop keeps iteratively parsing JSON even after condition is complete

I'm putting JSON data into a table view, and I'm trying to parse through the data using a for loop. However, when the loop is done parsing through the JSON data and has placed the 20 items into the table view, it restarts the process, parses the JSON again, and the same data appears in the table view again. This process repeats for a long time as well.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else{ return }
var searchURL = NSString(format: "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=%f,%f&radius=50000&types=night_club&key=MY_API_KEY", (location.coordinate.latitude),(location.coordinate.longitude)) as? String
var cityInfo = NSString(format: "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=%f,%f&radius=50000&types=locality&key=MY_API_KEY", (location.coordinate.latitude),(location.coordinate.longitude)) as? String
manager.stopUpdatingLocation()
getCityInfo(url: cityInfo!)
callAlamo(url: searchURL!)
}
func getCityInfo(url:String){
Alamofire.request(url).responseJSON(completionHandler: { response in
self.parseJSON(JSONData: response.data!)
})
}
func parseJSON(JSONData:Data){
do{
var readableJSON = try JSONSerialization.jsonObject(with: JSONData) as! JSONStandard
// PARSING THROUGH JSON DATA TO GET CITY NAME
if let results = readableJSON["results"] as? [JSONStandard]{
for i in 0..<results.count{
let item = results[i]
let cityInfo = item["name"] as! String
cityName.append(cityInfo)
// GETTING PHOTO URL WITH photo_reference AND PUTTING THEM INTO imageURL ARRAY
if let photos = item["photos"] as? [JSONStandard]{
for j in 0..<photos.count{
let photo = photos[j] as JSONStandard
let photoRef = photo["photo_reference"] as! String
let photoURL = NSString(format: "https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=%#&key=MY_API_KEY", photoRef) as? String
cityURL.append(photoURL!)
}
}
}
}
cityLabel.text = cityName[0]
cityImage.sd_setImage(with: URL(string:cityURL[0]), placeholderImage: #imageLiteral(resourceName: "cityOfCalgary"))
}
catch{
print(error)
}
}
func callAlamo(url:String){
Alamofire.request(url).responseJSON(completionHandler: { response in
self.parseData(JSONData: response.data!)
})
}
func parseData(JSONData:Data){
do{
var myReadableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! JSONStandard
// PARSING THROUGH JSON DATA TO GET NAMES AND PICTURES OF PLACES, THEN PUTTING
// THEM INTO AN ARRAY AND OUTPUTTING THEM ONTO TABLE VIEW CELL
if let results = myReadableJSON["results"] as? [JSONStandard]{
for i in 0..<results.count{ //results.count = 20
let item = results[i]
let names = item["name"] as! String
placeNames.append(names)
// GETTING PHOTO URL WITH photo_reference AND PUTTING THEM INTO imageURL ARRAY
if let photos = item["photos"] as? [JSONStandard]{
let photoRef = photos[0]
let photoReference = photoRef["photo_reference"] as! String
let photoURL = NSString(format: "https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=%#&key=MY_API_KEY", photoReference) as? String
imageURL.append(photoURL!)
}
if let geometry = item["geometry"] as? JSONStandard{
if let location = geometry["location"] as? [String : Any]{
let latitude = location["lat"] as? Double
let longitude = location["lng"] as? Double
}
}
}
}
// SHOULD BE PLACED AT THE END OF GATHERING DATA
locationManager.stopUpdatingLocation()
self.tableView.reloadData()
}
catch{
print(error)
}
}
UPDATE:
As vadian had mentioned in one of his first comments, parseData() was getting called multiple times. So I added
locationManager.delegate = nil
after I stop updating the location in the locationManager delegate function.
`
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else{ return }
searchURL = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(location.coordinate.latitude),\(location.coordinate.longitude)&radius=50000&types=night_club&key=MY_API_KEY"
cityInfo = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(location.coordinate.latitude),\(location.coordinate.longitude)&radius=50000&types=locality&key=MY_API_KEY"
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
getCityInfo(url: cityInfo)
callAlamo(url: searchURL)
}
`
Everything else remains the same after this.
As I suspected you are calling parseData multiple times. A solution is to stop monitoring the location right in the delegate method.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let searchURL = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(location.coordinate.latitude),\(location.coordinate.longitude)&radius=50000&types=night_club&key=AIzaSyA2LQsGK_I1ETnKPGbjWgFW9onZlHog6dg"
// var cityInfo = NSString(format: "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=%f,%f&radius=50000&types=locality&key=AIzaSyA2LQsGK_I1ETnKPGbjWgFW9onZlHog6dg", (location?.coordinate.latitude)!,(location?.coordinate.longitude)!) as? String
manager.stopUpdatingLocation()
callAlamo(url: searchURL)
}
I edited the body of the method a bit to avoid all question and exclamation marks.
Side-note: Basically do not annotate types the compiler can infer.

If statement is not changing global variable swift

I am trying to set up a function to get my current location in app delegate but when I print(city) at the bottom it returns the original initialized value in the global variable which is "hello", even though I updated the value under the CLGeocoder.
AppDelegate:
import UIKit
import CoreData
import CoreLocation
let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
var country = "hello"
var city = "hello"
func setupLocationManager(){
let locationManager = CLLocationManager()
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
// Below method will provide you current location.
func getLocation() -> [String]{
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
let selflocation = manager.location
let latitude: Double = selflocation!.coordinate.latitude
let longitude: Double = selflocation!.coordinate.longitude
print("current latitude :: \(latitude)")
print("current longitude :: \(longitude)")
let location = CLLocation(latitude: latitude, longitude: longitude) //changed!!!
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
print(location)
if error != nil {
print("Reverse geocoder failed with error" + (error?.localizedDescription)!)
}
let pm = placemarks![0]
let speed = (selflocation?.speed)!
city = pm.addressDictionary!["City"]! as! String
country = pm.addressDictionary!["Country"]! as! String
if (placemarks?.count)! > 0 {
}
else {
print("Problem with the data received from geocoder")
}
})
print(city)
return [city as! String, country as! String]
}
This is because the geocoding is done asynchronously, so the print(city) is being executed before the geocoding is completed. So I suggest you do this.
func getLocation(completion: #escaping (Array<String>)->()){
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
let selflocation = manager.location
let latitude: Double = selflocation!.coordinate.latitude
let longitude: Double = selflocation!.coordinate.longitude
let location = CLLocation(latitude: latitude, longitude: longitude)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if let error = error {
print(error.localizedDescription)
return
}
if let placemark = placemarks?.first {
if let country = placemark.country, let city = placemark.locality {
completion([city, country])
return
} else {
print("country or city was nil.")
}
} else {
print("Problem with the data received from geocoder")
}
})
}
So instead of calling getLocation() call
getLocation { (location) in
print(location)
}
The problem here is you are getting the value before a new location value is assigned to it. You have to wait a little bit to get the updated value.
reverseGeocodeLocation works asynchronously, so the print statement is actually happening before it finishes. If you have any logic that depends on the results, you'll probably need to put it inside the completion handler closure.
Like the other answer says, reverseGeocodeLocation works asynchronously, so you may want to move the print(city) inside the closure, such as after
else {
print("Problem with the data received from geocoder")
}

Swift - How to get OpenWeatherMap JSON Data?

The following app should get the user's current location and then display the Name and Temperature of that location using OpenWeatherMap.
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var location: UILabel!
#IBOutlet weak var temperature: UILabel!
var locationManager: CLLocationManager = CLLocationManager()
var startLocation: CLLocation!
func extractData(weatherData: NSData) {
let json = try? NSJSONSerialization.JSONObjectWithData(weatherData, options: []) as! NSDictionary
if json != nil {
if let name = json!["name"] as? String {
location.text = name
}
if let main = json!["main"] as? NSDictionary {
if let temp = main["temp"] as? Double {
temperature.text = String(format: "%.0f", temp)
}
}
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let latestLocation: AnyObject = locations[locations.count - 1]
let lat = latestLocation.coordinate.latitude
let lon = latestLocation.coordinate.longitude
// Put together a URL With lat and lon
let path = "http://api.openweathermap.org/data/2.5/weather?lat=\(lat)&lon=\(lon)&appid=2854c5771899ff92cd962dd7ad58e7b0"
print(path)
let url = NSURL(string: path)
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) in
dispatch_async(dispatch_get_main_queue(), {
self.extractData(data!)
})
}
task.resume()
}
func locationManager(manager: CLLocationManager,
didFailWithError error: NSError) {
}
override func viewDidLoad() {
super.viewDidLoad()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
startLocation = nil
}
}
I've been learning how to get data from OpenWeatherMap following this tutorial:
https://www.youtube.com/watch?v=r-LZs0De7_U
The app crashes at:
self.extractData(data!)
as data is equal to nil, this shouldn't be happening as when I copy and paste the printed path into my web browser, the data is there. I'm sure I've followed the tutorial correctly, so what's the problem and how do I fix it?
The problem is with Transport Security - which causes issues for lots of us. Here's one of the SO answers explaining how to resolve it Transport security has blocked a cleartext HTTP
If you make the setting in your plist - set the NSAllowsArbitraryLoads key to YES under NSAppTransportSecurity dictionary in your .plist file - then it works.

Resources