Search code examples
iosswiftasynchronousios9

Swift - Updating the view with data from an async method


I'm new to swift and working with async code.

I have a method that grabs data from an API, puts the data into objects and puts those objects into an array. I would then like to update the UI with data from those objects in the array.

To illustrate the UI getting updated, I'll change the value of a label.

Because the method that grabs info from the API is async, I can't return a value from it. Instead I pass a callback:

@IBOutlet var labelTest: UILabel!
private var eventsArray : Array<Event> = Array<Event>()

override func viewDidLoad() {

    APIAccessUtil.getEventsFromAPI({(eventsList: Array<Event>) -> Void in
        self.eventsArray = eventsList
    })

    sleep(5) //this is of course not good, but I'll leave it here to illustrate what's going on. 

    self.labelTest.text = eventsArray[0].description

}

This works because making the thread sleep will kind of leave time for the array to be filled up. However, making the main thread sleep is a really bad idea and there is no guarantee that data will be returned in this time.

If the sleep(...) is removed, then the UI label is attempted to be updated before the getEventsFromAPI call has completed, producing a runtime error (since the array will still be nil).

Attempt 2:

@IBOutlet var labelTest: UILabel!
private var eventsArray : Array<Event> = Array<Event>()

override func viewDidLoad() {

    APIAccessUtil.getEventsFromAPI({(eventsList: Array<Event>) -> Void in
        self.eventsArray = eventsList
        self.labelTest.text = eventsArray[0].description
    }) 
}

In attempt #2, I'm updating the UI from a background thread, which isn't good and produces the following warning:

This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.

So, is there any way to update the UI from the main thread (i.e. change the label text) after the getEventsFromAPI call has completed (so that the array instance variable now is not nil)?


Solution

  • You are almost there. The final trick is to call the UI update from the main thread:

    override func viewDidLoad() {
    
        APIAccessUtil.getEventsFromAPI({(eventsList: Array<Event>) -> Void in
            self.eventsArray = eventsList
    
            // Update the UI on the main thread
            DispatchQueue.main.async {          
                self.labelTest.text = eventsArray[0].description
            }           
        })
    }