Message from debugger: Terminated due to memory issue - ios

My app working with Geojson file. I use MapBox SDK to add MGLPolyline to map. But the problem is my file too large, so that the app crash and got the error: Message from debugger: Terminated due to memory issue. I faced with 66234 objects at first loop. I tried to chunk the array to new array but not success. Please help me to solve the prolem. Here is my code for draw on map and here is my test project on github use Xcode 8.1 If have any different 3rd party which can solve my prolems is welcome too:
func drawPolyline() {
// Parsing GeoJSON can be CPU intensive, do it on a background thread
DispatchQueue.global(qos: .background).async {
// Get the path for example.geojson in the app's bundle
let jsonPath = Bundle.main.path(forResource: "KMLMAPNew", ofType: "json")
let jsonData = NSData(contentsOfFile: jsonPath!)
do {
// Load and serialize the GeoJSON into a dictionary filled with properly-typed objects
guard let jsonDict = try JSONSerialization.jsonObject(with: jsonData! as Data, options: []) as? Dictionary<String, AnyObject>, let features = jsonDict["features"] as? Array<AnyObject> else{return}
for feature in features {
guard let feature = feature as? Dictionary<String, AnyObject>, let geometry = feature["geometry"] as? Dictionary<String, AnyObject> else{ continue }
if geometry["type"] as? String == "LineString" {
// Create an array to hold the formatted coordinates for our line
var coordinates: [CLLocationCoordinate2D] = []
if let locations = geometry["coordinates"] as? Array<AnyObject> {
// Iterate over line coordinates, stored in GeoJSON as many lng, lat arrays
for location in locations {
// Make a CLLocationCoordinate2D with the lat, lng
if let location = location as? Array<AnyObject>{
let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)
// Add coordinate to coordinates array
coordinates.append(coordinate)
}
}
}
let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
// Optionally set the title of the polyline, which can be used for:
// - Callout view
// - Object identification
line.title = "Crema to Council Crest"
// Add the annotation on the main thread
DispatchQueue.main.async {
// Unowned reference to self to prevent retain cycle
[unowned self] in
self.mapboxView.addAnnotation(line)
}
}
}
}
catch
{
print("GeoJSON parsing failed")
}
}
}
EDIT::#Alessandro Ornano and #fragilecat thanks so much. But those solutions still cannot solve the terminate of the app on iPad. I think it so hard to change the current code to get it to work properly, because the data is so large. I think I will need another solution that works with big data. Like chunking the array into the small arrays then loading them by queue. But I don't know how to start :(
I send an email to the support team at MapBox, asking for suggestions.

One thing I have learnt from creating memory intensive apps is that you have to use autoreleasepool every time you create variables inside loops, if these loops are long
Review all your code and transform things like
func loopALot() {
for _ in 0 ..< 5000 {
let image = NSImage(contentsOfFile: filename)
}
}
into
func loopALot() {
for _ in 0 ..< 5000 {
autoreleasepool {
let image = NSImage(contentsOfFile: filename)
}
}
}
Review all kinds of loops for, while, etc.
This will force of iOS to release the variable and its correspondent memory usage at the end of every turn of the loop, instead of holding the variable and its memory usage until the function ends. That will reduce dramatically your memory usage.

The problem here is related to effective memory management. You are loading a lot of data via your json file. You realized that you needed to do the majority of the work on a background queue (thread) however the issue is how you are updating the UI via DispatchQueue.main.async function. In the current version of the drawPolyline() method you are switching between the background queue and the main queue 66234 times, given the number of objects in your first loop. Also you were creating the same number of CLLocationCoordinate2D arrays.
This leads to a huge memory footprint. You do not mention any requirements in regards to how you render the lines. So if we restructure your drawPolyline() method to use a instance variable for the CLLocationCoordinate2D array so we only use one and then we process all of the json file before we update the UI. The memory usage dropped down to a some what more manageable 664.6 MB.
Of course the rendering may not be exactly what you are looking for and if that's the case you might want to restructure your CLLocationCoordinate2D array into a more suitable data structure.
Below is your ViewController class with the rewritten drawPolyline() as drawPolyline2()
import UIKit
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
#IBOutlet var mapboxView: MGLMapView!
fileprivate var coordinates = [[CLLocationCoordinate2D]]()
fileprivate var jsonData: NSData?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
mapboxView = MGLMapView(frame: view.bounds)
mapboxView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// mapboxView.setCenter(CLLocationCoordinate2D(latitude: 45.5076, longitude: -122.6736),
// zoomLevel: 11, animated: false)
mapboxView.setCenter(CLLocationCoordinate2D(latitude: 1.290270, longitude: 103.851959),
zoomLevel: 11, animated: false)
view.addSubview(self.mapboxView)
mapboxView.delegate = self
mapboxView.allowsZooming = true
drawPolyline2()
//newWay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func drawPolyline2() {
DispatchQueue.global(qos: .background).async {
if let path = Bundle.main.path(forResource: "KMLMAPNew", ofType: "json") {
let fileURL = URL(fileURLWithPath: path)
if let data = try? Data(contentsOf: fileURL) {
do {
let dictionary = try JSONSerialization.jsonObject(with: data as Data, options: []) as? Dictionary<String, AnyObject>
if let features = dictionary?["features"] as? Array<AnyObject> {
print("** START **")
for feature in features {
guard let feature = feature as? Dictionary<String, AnyObject>, let geometry = feature["geometry"] as? Dictionary<String, AnyObject> else { continue }
if geometry["type"] as? String == "LineString" {
// Create an array to hold the formatted coordinates for our line
if let locations = geometry["coordinates"] as? Array<AnyObject> {
// Iterate over line coordinates, stored in GeoJSON as many lng, lat arrays
var featureCoordinates = [CLLocationCoordinate2D]()
for location in locations {
// Make a CLLocationCoordinate2D with the lat, lng
if let location = location as? Array<AnyObject>{
let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)
// Add coordinate to coordinates array
featureCoordinates.append(coordinate)
}
}
// Uncomment if you need to store for later use.
//self.coordinates.append(featureCoordinates)
DispatchQueue.main.async {
let line = MGLPolyline(coordinates: &featureCoordinates, count: UInt(featureCoordinates.count))
// Optionally set the title of the polyline, which can be used for:
// - Callout view
// - Object identification
line.title = "Crema to Council Crest"
self.mapboxView.addAnnotation(line)
}
}
}
}
print("** FINISH **")
}
} catch {
print("GeoJSON parsing failed")
}
}
}
}
}
func drawSmallListObj(list: [Dictionary<String, AnyObject>]){
for obj in list{
// print(obj)
if let feature = obj as? Dictionary<String, AnyObject> {
if let geometry = feature["geometry"] as? Dictionary<String, AnyObject> {
if geometry["type"] as? String == "LineString" {
// Create an array to hold the formatted coordinates for our line
var coordinates: [CLLocationCoordinate2D] = []
if let locations = geometry["coordinates"] as? Array<AnyObject> {
// Iterate over line coordinates, stored in GeoJSON as many lng, lat arrays
for location in locations {
// Make a CLLocationCoordinate2D with the lat, lng
if let location = location as? Array<AnyObject>{
let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)
// Add coordinate to coordinates array
coordinates.append(coordinate)
}
}
}
let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
// Optionally set the title of the polyline, which can be used for:
// - Callout view
// - Object identification
line.title = "Crema to Council Crest"
// Add the annotation on the main thread
DispatchQueue.main.async {
// Unowned reference to self to prevent retain cycle
[unowned self] in
self.mapboxView.addAnnotation(line)
}
}
}
}
}
}
func mapView(_ mapView: MGLMapView, alphaForShapeAnnotation annotation: MGLShape) -> CGFloat {
// Set the alpha for all shape annotations to 1 (full opacity)
return 1
}
func mapView(_ mapView: MGLMapView, lineWidthForPolylineAnnotation annotation: MGLPolyline) -> CGFloat {
// Set the line width for polyline annotations
return 2.0
}
func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor {
// Give our polyline a unique color by checking for its `title` property
if (annotation.title == "Crema to Council Crest" && annotation is MGLPolyline) {
// Mapbox cyan
return UIColor(red: 59/255, green:178/255, blue:208/255, alpha:1)
}
else
{
return UIColor.red
}
}
}

I had some problems to test your project with pods, so I've directly remove pods and use Mapbox framework directly from here.
I've no problems for the first launches both in simulator and real iPad (my iPad 4 gen.) but after a while I've your same error, so I've correct this code with:
DispatchQueue.main.async {
// weaked reference to self to prevent retain cycle
[weak self] in
guard let strongSelf = self else { return }
strongSelf.mapboxView.addAnnotation(line)
}
because unowned it's not enough to prevent retain cycle.
Now seems it works well.
Hope it helps.
P.S. (I've used the latest available Mapbox v3.3.6)
Update (after comments):
So, first of all I make all my test with Mapbox framework inserted as "embedded framework".
I've make some corrections to your github project only to ViewController.swift to avoid retain cycles.
P.S. I remove comments lines to make easy reading:
func drawPolyline() {
DispatchQueue.global(qos: .background).async {
[weak self] in
guard let strongSelf = self else { return }
let jsonPath = Bundle.main.path(forResource: "KMLMAPNew", ofType: "json")
let jsonData = NSData(contentsOfFile: jsonPath!)
do {
guard let jsonDict = try JSONSerialization.jsonObject(with: jsonData! as Data, options: []) as? Dictionary<String, AnyObject>, let features = jsonDict["features"] as? Array<AnyObject> else{return}
for feature in features {
guard let feature = feature as? Dictionary<String, AnyObject>, let geometry = feature["geometry"] as? Dictionary<String, AnyObject> else{ continue }
if geometry["type"] as? String == "LineString" {
var coordinates: [CLLocationCoordinate2D] = []
if let locations = geometry["coordinates"] as? Array<AnyObject> {
for location in locations {
if let location = location as? Array<AnyObject>{
let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)
coordinates.append(coordinate)
}
}
}
let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
line.title = "Crema to Council Crest"
print(feature) // Added this line just for debug to see the flow..
DispatchQueue.main.async {
strongSelf.mapboxView.addAnnotation(line)
}
}
}
}
catch
{
print("GeoJSON parsing failed")
}
}
}
func newWay(){
DispatchQueue.global(qos: .background).async {
[weak self] in
guard let strongSelf = self else { return }
let jsonPath = Bundle.main.path(forResource: "KMLMAPNew", ofType: "json")
let jsonData = NSData(contentsOfFile: jsonPath!)
do {
if let jsonDict = try JSONSerialization.jsonObject(with: jsonData! as Data, options: []) as? Dictionary<String, AnyObject> {
if let features = jsonDict["features"] as? Array<AnyObject> {
let chunks = stride(from: 0, to: features.count, by: 2).map {
Array(features[$0..<min($0 + 2, features.count)])
}
for obj in chunks{
strongSelf.drawSmallListObj(list: obj as! [Dictionary<String, AnyObject>])
}
}
}
}
catch
{
print("GeoJSON parsing failed")
}
}
}
func drawSmallListObj(list: [Dictionary<String, AnyObject>]){
for obj in list{
if let feature = obj as? Dictionary<String, AnyObject> {
if let geometry = feature["geometry"] as? Dictionary<String, AnyObject> {
if geometry["type"] as? String == "LineString" {
var coordinates: [CLLocationCoordinate2D] = []
if let locations = geometry["coordinates"] as? Array<AnyObject> {
for location in locations {
if let location = location as? Array<AnyObject>{
let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)
coordinates.append(coordinate)
}
}
}
let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
line.title = "Crema to Council Crest"
DispatchQueue.main.async {
[weak self] in
guard let strongSelf = self else { return }
strongSelf.mapboxView.addAnnotation(line)
}
}
}
}
}
}

Will share my experience with this strange issue.
For me the app crashed with "Message from debugger: Terminated due to memory issue" and instruments didn't help a lot. As well the Memory - was within the green limits. So I was not sure what is causing that. And it was not possible to debug, and single-device specific issue.
Just restarted the iPhone 6 - and the issue disappeared for now.

First Solution
Maybe your for loop is running infinitely and allocating memory to an array with nil value every time. It is using high amounts of memory, so it gives this error.
Please check by print something in the for loop.
Second Solution
Add this in didReceiveMemoryWarning:
NSURLCache.sharedURLCache().removeAllCachedResponses()
NSURLCache.sharedURLCache().diskCapacity = 0
NSURLCache.sharedURLCache().memoryCapacity = 0
You can also change the cache policy of the NSURLRequest:
let day_url = NSURL(string: "http://www.example.com")
let day_url_request = NSURLRequest(URL: day_url,
cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 10.0)
let day_webView = UIWebView()
day_webView.loadRequest(day_url_request)
More information on cache policies here.

make you stuff on callout this means execute polyne only when click on the pin
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)

I was receiving this error and was very confused since my app's memory usage was fairly small.
In the end, I discovered that it was due to me loading a number of files as mapped memory, e.g:
let data = try Data(contentsOf: url, options: .mappedIfSafe)
I don't know why I was getting these bizarre crashes, but just loading the data normally prevented the crashes from happening.

Related

How to delete the last annotation made on a MapView?

#IBAction func addPin(_ sender: UILongPressGestureRecognizer) {
let location = sender.location(in: self.mapView)
let locCoord = self.mapView.convert(location, toCoordinateFrom: self.mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = locCoord
annotation.title = titleTextField.text
var annotationsArray: [[String:Any]]!
var annotationsData = UserDefaults.standard.data(forKey: "StoredAnnotations")
//If the data is nil, then set the new annotation as the only element in the array
if annotationsData == nil {
annotationsArray = [newAnnotationDict]
} else {
//If it isn't nil, then convert the data into an array of dicts
do {
//Convert this data into an array of dicts
annotationsArray = try JSONSerialization.jsonObject(with: annotationsData!, options: []) as! [[String:Any]]
annotationsArray.append(newAnnotationDict)
} catch {
print(error.localizedDescription)
}
}
do {
//Use JSONSerialization to convert the annotationsArray into Data
let jsonData = try JSONSerialization.data(withJSONObject: annotationsArray, options: .prettyPrinted)
//Store this data in UserDefaults
UserDefaults.standard.set(jsonData, forKey: "StoredAnnotations")
} catch {
print(error.localizedDescription)
}
}
#IBAction func deleteLast(sender: UIButton){
}
I am currently creating annotations on a MapView using the code given above. I would like to delete the previous annotation that was created by the user. How would I do this by using the deleteLast button?

How to draw a polyline between multiple markers?

Want to draw a PolyLine from userLocation to multiple marker. In my code already added markers coordinates in a array then added userLocation into 0th position of that array. Now I want to draw a route polyLine between array elements. My code is given below...
self.coods.append(self.currentLocation)
let jsonResponse = response.data
do{
let json = try JSON(data: jsonResponse!)
self.dictXYZ = [json]
print("JsonResponse printed \(json["data"][0]["lattitude"])")
if let array = json["data"].array{
for i in 0..<array.count{
var coordinate = CLLocationCoordinate2D()
coordinate.latitude = array[i]["lattitude"].doubleValue
coordinate.longitude = array[i]["longitude"].doubleValue
self.coods.append(coordinate)
}
for j in self.coods {
let marker = GMSMarker()
marker.position = j
let camera = GMSCameraPosition.camera(withLatitude: j.latitude, longitude: j.longitude, zoom: 12)
self.mapView.camera = camera
marker.map = self.mapView
}
let path = GMSMutablePath()
for j in self.coods {
path.add(j)
}
let polyline = GMSPolyline(path: path)
polyline.map = mapView
In the Google Developer Docs.
Waypoints - Specifies an array of intermediate locations to include along the route between the origin and destination points as
pass through or stopover locations. Waypoints alter a route by
directing it through the specified location(s). The API supports
waypoints for these travel modes: driving, walking and bicycling; not
transit.
First you need to create a waypoints for all intermediate locations to add the route between the source and destination. With that polyline you can create a GMSPath and then draw the route by using GMSPolyline. I hope below solution can help you to draw a route for multiple locations.
func getPolylineRoute(from source: CLLocationCoordinate2D, to destinations: [CLLocationCoordinate2D], completionHandler: #escaping (Bool, String) -> ()) {
guard let destination = destinations.last else {
return
}
var wayPoints = ""
for (index, point) in destinations.enumerated() {
if index == 0 { // Skipping first location that is current location.
continue.
}
wayPoints = wayPoints.count == 0 ? "\(point.latitude),\(point.longitude)" : "\(wayPoints)%7C\(point.latitude),\(point.longitude)"
}
let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(source.latitude),\(source.longitude)&destination=\(destination.latitude),\(destination.longitude)&sensor=true&mode=driving&waypoints=\(wayPoints)&key=\(GOOGLE_API_KEY)")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed : \(String(describing: error?.localizedDescription))")
return
} else {
do {
if let json: [String: Any] = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] {
guard let routes = json["routes"] as? [[String: Any]] else { return }
if (routes.count > 0) {
let overview_polyline = routes[0]
let dictPolyline = overview_polyline["overview_polyline"] as? NSDictionary
let points = dictPolyline?.object(forKey: "points") as? String
completionHandler(true, points!)
} else {
completionHandler(false, "")
}
}
} catch {
print("Error : \(error)")
}
}
}
task.resume()
}
Pass the current location and destination array of locations to getPolylineRoute method. Then call the drawPolyline method with polyline points from main thread.
getPolylineRoute(from: coods[0], to: coods) { (isSuccess, polylinePoints) in
if isSuccess {
DispatchQueue.main.async {
self.drawPolyline(withMapView: self.mapView, withPolylinePoints: polylinePoints)
}
} else {
print("Falied to draw polyline")
}
}
func drawPolyline(withMapView googleMapView: GMSMapView, withPolylinePoints polylinePoints: String){
path = GMSPath(fromEncodedPath: polylinePoints)!
let polyline = GMSPolyline(path: path)
polyline.strokeWidth = 3.0
polyline.strokeColor = .lightGray
polyline.map = googleMapView
}
First create GMSPath object
let path = GMSMutablePath()
self.coods.forEach {
path.add(coordinate: $0)
}
https://developers.google.com/maps/documentation/ios-sdk/reference/interface_g_m_s_mutable_path.html#af62038ea1a9da3faa7807b8d22e72ffb
Then Create GMSPolyline object using path
let pathLine = GMSPolyline.with(path: path)
pathLine.map = self.mapView
https://developers.google.com/maps/documentation/ios-sdk/reference/interface_g_m_s_polyline.html#ace1dd6e6bab9295b3423712d2eed90a4

How to show an user Location on Live. like Uber IOS Swift

I am new to swift and Maps. I am facing problem with displaying user live location. I have to display user location like Uber and Ola. I am getting array of coordinates from server.
This is the way i am fetching coordinates from server. I want to show moving user location. see following my code.
func SetUpMapsUI()
{
AdminAPIManager.sharedInstance.getAdminRunningStatusFromURL(){(resignationsJson)-> Void in
let swiftyJsonVar = JSON(resignationsJson)
let status = swiftyJsonVar["status"].rawString() as! String
print("status",status)
let message = swiftyJsonVar["message"].rawString()
if status.isEqual("0"){
if (message?.isEqual("No trips done so far."))!
{
self.mapViewBottomCons.constant = 0
}else
{
self.mapViewBottomCons.constant = 70
}
self.Bottom_view.isHidden = true
Toast.short(message: message as! String)
return
}
let busVar = swiftyJsonVar["bus_details"].rawString()!
let jsonData = busVar.data(using: .utf8)!
let LocationArray = try? JSONSerialization.jsonObject(with: jsonData, options: []) as! Array< Any>
for data in LocationArray!
{
let dic = data as! NSDictionary
guard let lat = dic.value(forKey: "latitude") as? Double else {
return
}
print("latlatlatlat",lat)
guard let lon = dic.value(forKey: "longitude") as? Double else {
return
}
print("longitude",lon)
self.arrayMapPath.append(NewMapPath(lat: Double(lat), lon: Double(lon)))
}
if self.arrayMapPath.count > 0
{
self.drawPathOnMap()
}
}
here is library that does that functionality it is written for both swift and ObjC..
ARCarMovement
here is a Stackoverflow SO answer

Getting an error when trying to pull data from a JSON Swift [duplicate]

This question already has an answer here:
Json Serialisation Swift 3 type error
(1 answer)
Closed 5 years ago.
So I've been getting an error for a thread 4: SIGABRT on Xcode when trying to parse data from the OpenWeatherApp API. The error that pulls up on the console is:
Could not cast value of type '__NSArrayM' (0x3419714) to 'NSDictionary' (0x3419958)
I looked at different things on this forum already and none of it seems to really work.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var typeCity: UITextField!
var weatherDescription : String = ""
override func viewDidLoad() {
super.viewDidLoad()
// let checkText = typeCity.text
/*
if typeCity?.text == nil{
typeCity.placeholder = "Type a city with no spaces"
let city = "Chicago"
}
*/
let city = "Chicago"
/*
if checkText != nil {
typeCity?.text = city
}
*/
print("City: \(city)")
// Do any additional setup after loading the view, typically from
a nib.
let url = URL(string:
"http://api.openweathermap.org/data/2.5/weather?q=\
(city)&appid=626a124ef0844d2e021329c38a5dfafd")
let task = URLSession.shared.dataTask(with: url!) { (data,
response, error) in
if error != nil{
print(error!)
} else {
if let urlContent = data {
do {
let jsonResult = try
JSONSerialization.jsonObject(with: urlContent, options:
JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
//let lon = jsonResult["coord"]["lon"].double
//let lat = jsonResult["coord"]["lon"].double
//let temp = jsonResult?["main"]["double"].double
//print
print(jsonResult["name"]!!)
let coordinates = jsonResult["coord"] as! [String:Any]//the coordinates parsing
print("Coordinates: \(coordinates)")
let lon = coordinates["lon"] as! Double
let lat = coordinates["lat"] as! Double
print("Latitude: \(lat) Longitude: \(lon)")
let main = jsonResult["main"] as!
[String:Any]//for the temperature
let kelvin = main["temp"] as! Double
let degreesFahrenheit = 9/5 * (kelvin-273) + 32
print("Temperature: \(degreesFahrenheit)")
let humidity = main["humidity"] as! Double
let pressure = main["pressure"] as! Double
let temp_max = main["temp_max"] as! Double
let temp_min = main["temp_min"] as! Double
let description = jsonResult["weather"]
["description"]as! [String: Any]
print("description")
} catch{
print("Json Processing has failed or city name not recognized.")
}
// let json = jsonResult(data: data)
//print("Lat: \(String(describing: lat)) Lon: \
(String(describing: lon)) Temp: \(String(describing: temp))")
}
}
}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}}
There seems to be an error with the following line:
let description = jsonResult["weather"]["description"]as! [String: Any]
Thank you in advance for all the help!
You are trying to implicitly convert an array to a dictionary. Try to safely check the type of it:
if let description = jsonResult["weather"]["description"] as? [String] {
[...]
}

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.

Resources