Julian
Julian

Reputation: 77

Getting " modifying the autolayout engine from a background thread after the engine was accessed from the main thread" even while using DispatchQueue

Hello I'm trying to create a simple app that gets annotations from my web server and then propagates the map with them. The only problem is when I call the function bob 30 seconds later to get a new annotation from another location it gives me the error above, I tried to fix it using DispatchQueue.main.async to no avail. Any help is appreciated.

Here is the function in question

// this is the test to see if it can add a new annotation after 30 seconds
if bob == 30{

    let user_lat_temp = 26.7709
    let user_lng_temp = -80.1067

    DispatchQueue.main.async() {
        // Do stuff to UI
        self.GetAnnotations(lat: user_lat_temp, lng: user_lng_temp)
    }

    // reset it to see if it breaks
    bob = 0
    }
    bob = bob + 1
    print("bob: ", bob)
}

Here is the full code

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {



    @IBOutlet weak var mapView: MKMapView!
    let access_token = ""
    let manager = CLLocationManager()
    var firstTime = 1
    var user_lat = 0.0
    var user_lng = 0.0

    var bob = 0

    override func viewDidLoad() {

        super.viewDidLoad()

        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.requestWhenInUseAuthorization()

        manager.startUpdatingLocation()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        let userLocation = locations[0]


        user_lat = userLocation.coordinate.latitude
        user_lng = userLocation.coordinate.longitude

        self.mapView.showsUserLocation = true

        if firstTime == 1{

            GetAnnotations(lat: user_lat, lng: user_lng)

            firstTime = 0
        }


       // this is the test to see if it can add a new annotation after 30 seconds
   if bob == 30{
        let user_lat_temp = 26.7709
        let user_lng_temp = -80.1067

        DispatchQueue.main.async() {
            // Do stuff to UI
            self.GetAnnotations(lat: user_lat_temp, lng: user_lng_temp)
        }

        // reset it to see if it breaks
        bob = 0
        }
        bob = bob + 1
        print("bob: ", bob)
    }

    func GetAnnotations(lat: Double, lng: Double){

        guard let url = URL(string: "http://192.168.1.10:7888/api/?controller=location&action=get_locations") else {return}

        var request = URLRequest(url: url)

        request.httpMethod = "POST"

        let postString = "access_token=\(access_token)&lat=\(lat)&lng=\(lng)";

        request.httpBody = postString.data(using: String.Encoding.utf8)

        URLSession.shared.dataTask(with: request) { (data, response, err) in

            if let error = err {
                print("the server is not responding \(error)")
            }

            if let response = response {

                // if the user has a bad access token or is logged out
                if let httpResponse = response as? HTTPURLResponse {
                    if httpResponse.statusCode == 401{
                        print("bad access token")
                        return
                    }
                }else{
                    print("the server is not responding")
            }
            print(response)




            guard let data = data else { return }

            // parse the json for the locations
            do {
                let mapJSON = try JSONDecoder().decode(parseJsonLocations.self, from: data)
                let user_id = mapJSON.user_info.user_id

                print(user_id)
                print(mapJSON.locations.count)

             // do map 
              let distanceSpan:CLLocationDegrees = 10000
               let userLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat, lng)
                self.mapView.setRegion(MKCoordinateRegionMakeWithDistance(userLocation, distanceSpan, distanceSpan), animated: true)
               self.mapView.delegate = self

                var i = 0

                while i < mapJSON.locations.count {

                    let location_id = mapJSON.locations[i].location_id
                    let location_name = mapJSON.locations[i].location_name
                    let location_lat = mapJSON.locations[i].lat
                    let location_lng = mapJSON.locations[i].lng

                    let locationsLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location_lat, location_lng)

                    let subtitle = "location_id: \(location_id)"

                    let userAnnotation = Annotation(title: location_name, subtitle: subtitle, coordinate: locationsLocation)

                    self.mapView.addAnnotation( userAnnotation )

                    i = i + 1

                }

            } catch {
                print("error trying to convert data to JSON")
                print(error)
            }





        }
        }.resume()
    }


}

Upvotes: 0

Views: 58

Answers (2)

Paulw11
Paulw11

Reputation: 114866

You have the right idea; explicitly dispatch the UI updates on the main queue, unfortunately you have dispatched the wrong function.

GetAnnotations (Which, by convention should be getAnnotations) makes an asynchronous network call via the URLSession DataTask, with the results returned in the completion handler. With network operations such as these there is a good chance that the completion handler will not be called on the main queue and that is the case here.

Since the completion handler is not executing on the main queue and you update the UI, you get the error message.

You need to dispatch those UI operations on the main queue

For example:

guard let data = data else { return }

DispatchQueue.main.async {
        // parse the json for the locations
        do {
            let mapJSON = try JSONDecoder().decode(parseJsonLocations.self, from: data)
            let user_id = mapJSON.user_info.user_id

            print(user_id)
            print(mapJSON.locations.count)

         // do map 
          let distanceSpan:CLLocationDegrees = 10000
           let userLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat, lng)
            self.mapView.setRegion(MKCoordinateRegionMakeWithDistance(userLocation, distanceSpan, distanceSpan), animated: true)
           self.mapView.delegate = self

            var i = 0

            while i < mapJSON.locations.count {

                let location_id = mapJSON.locations[i].location_id
                let location_name = mapJSON.locations[i].location_name
                let location_lat = mapJSON.locations[i].lat
                let location_lng = mapJSON.locations[i].lng

                let locationsLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location_lat, location_lng)

                let subtitle = "location_id: \(location_id)"

                let userAnnotation = Annotation(title: location_name, subtitle: subtitle, coordinate: locationsLocation)

                self.mapView.addAnnotation( userAnnotation )

                i = i + 1

            }

        } catch {
            print("error trying to convert data to JSON")
            print(error)
        }
}

In the case of the location manager delegate call, the documentation states:

The methods of your delegate object are called from the thread in which you started the corresponding location services. That thread must itself have an active run loop, like the one found in your application’s main thread.

So, as long as you called startUpdatingLocation from the main queue, your delegate callbacks will be on the main queue

Upvotes: 1

matt
matt

Reputation: 535232

There are a lot of other places where you are not paying attention to the question of what thread you might be on.

You are saying

if firstTime == 1 {
    GetAnnotations( // ...

without making sure you're on the main thread.

And then, inside GetAnnotations, you are saying

self.mapView.setRegion
// ... and all that follows ...

without making sure you're on the main thread.

I'm not saying you're not on the main thread at those moments, but there is no reason to think that you are, either. You should check and get it sorted out.

Upvotes: 2

Related Questions