Carpsen90 Carpsen90 - 2 months ago 19
iOS Question

Updating a UILabel from an escaping closure doesn't take effect immedialtely

Basically:

In viewDidAppear:

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
myFunction(closure)
}


closure
is of type
@escaping (_ success: Bool) -> Void


In the closure code:

print("Change myLabel.text")
self?.myLabel.text = "New Title"
print("myLabel.text changed")


"Show myLabel.text"
and
"myLabel.text changed"
are printed as soon as the VC appears, but the text in
myLabel
changes after several seconds (around 10 seconds).

myLabel
is created programmatically as seen below:

class MyClass : UIViewController {
...
var myLabel: UILabel!
var contacts = [ContactEntry]()
...
override func viewWillLayoutSubviews() {
myLabel = UILabel()
myLabel.text = "Original title"
myLabel.frame = CGRect(x: 10, y: 10, width: 100, height: 400)
self.view.addSubview(myLabel)
}
}


The actual code is inspired from here:

viewDidAppear:

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
requestAccessToContacts { [weak self] (success) in
if success {
self?.retrieveContacts(completion: { (success, contacts) in
self?.tableView.isHidden = false
self?.myLabel.isHidden = true
if success && (contacts?.count)! > 0 {
self?.contacts = contacts!
self?.myLabel.text = ""
self?.myLabel.isHidden = true
self?.tableView.reloadData()
} else if (contacts?.count)! == 0 {
self?.myLabel.isHidden = false
self?.myLabel.text = "No contacts found"
} else {{
self?.myLabel.isHidden = false
self?.myLabel.text = "Error loading contacts"
}
})
} else {
print("Change label text")
self?.myLabel.attributedText = "Enable access to contacts by going to\nSettings>Privacy>Contacts>MyApp"
self?.myLabel.isHidden = false
print("Label text changed")
}
}
}


requestAccessToContacts:

func requestAccessToContacts(completion: @escaping (_ success: Bool) -> Void) {

let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts)

switch authorizationStatus {
case .authorized:
// authorized previously
completion(true)

case .denied, .notDetermined:
// needs to ask for authorization
self.contactStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (accessGranted, error) -> Void in
completion(accessGranted)
})

default:
// not authorized.
completion(false)
}
}


retrieveContacts:

func retrieveContacts(completion: (_ success: Bool, _ contacts: [ContactEntry]?) -> Void) {
var contacts = [ContactEntry]()
do {
let contactsFetchRequest = CNContactFetchRequest(keysToFetch: [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactImageDataKey, CNContactImageDataAvailableKey, CNContactPhoneNumbersKey, CNContactEmailAddressesKey].map {$0 as CNKeyDescriptor})
try contactStore.enumerateContacts(with: contactsFetchRequest, usingBlock: { (cnContact, error) in
if let contact = ContactEntry(cnContact: cnContact) { contacts.append(contact) }
})
completion(true, contacts)
} catch {
completion(false, nil)
}
}


What am I missing here?

Answer

You are saying:

print("Change myLabel.text")
self?.myLabel.text = "New Title"
print("myLabel.text changed")

And you are complaining that the print messages appear in the console but the label doesn't change until much later.

This sort of delay is nearly always caused by a threading issue. You do not show MyFunction and you do not show the entirety of closure, so it's impossible to help you in detail, but the likelihood is that you are messing around with background threads without knowing what you are doing, and that you have accidentally set myLabel.text on a background thread, which is a big no-no. You must step out to the main thread in order to touch the interface in any way:

DispatchQueue.main.async {
    print("Change myLabel.text")
    self?.myLabel.text = "New Title"
    print("myLabel.text changed")
    // ... and everything else that touches the interface
}