In a model's class Location, I get the name of the current city:
var currentLatitude: Double!
var currentLongitude: Double!
var currentLocation: String!
var currentCity: String!
func getLocationName() {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)
geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
guard let addressDict = placemarks?[0].addressDictionary else {
return
}
if let city = addressDict["City"] as? String {
self.currentCity = city
print(city)
}
if let zip = addressDict["ZIP"] as? String {
print(zip)
}
if let country = addressDict["Country"] as? String {
print(country)
}
self.nowUpdateUI()
})
}
In view controller I want to update the UI and update my label to show the current city.
However, self.currentCity = city happens inside of a closure. So if I just run a func in view controller:
func updateUI() {
cityLbl.text = Location.sharedInstance.currentCity
}
- I'm not getting anywhere because the closure haven't finished running.
I've been advised to add a completion handler to
getLocationName()and inside of it, perform the call to a func that will update the UI. However, from all the tutorials out there on closures, completion handlers, it is not clear to me how to achieve that. How to construct a completion handler, pass it as an arg togetLocationName()and how to callgetLocationNamefrom view controller?
To handle this situation you have multiple option.
Create
delegate/protocolwith your Location class- Create one protocol and implement that protocol method with your
ViewControllerand declare its instance in yourLocationclass. After then in thecompletionHandlerofreverseGeocodeLocationcall this delegate method. Check Apple documentation onProtocolfor more details.
- Create one protocol and implement that protocol method with your
You can create
completionHandlerwith yourgetLocationNamemethod ofLocationclass.Add
completionHandlerwithgetLocationNameand called thatcompletionHandlerinside thecompletionHandlerofreverseGeocodeLocationlike this way.func getLocationName(completionHandler: @escaping (_ success: Bool) -> Void) { let geoCoder = CLGeocoder() let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude) geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in guard let addressDict = placemarks?[0].addressDictionary else { completionHandler(false) return } if let city = addressDict["City"] as? String { self.currentCity = city print(city) } if let zip = addressDict["ZIP"] as? String { print(zip) } if let country = addressDict["Country"] as? String { print(country) } completionHandler(true) //self.nowUpdateUI() }) }Now in
ViewControllerwhere you are calling this function call yourupdateUImethod inside the completion block.Location.sharedInstance.getLocationName { (success) in if success {//If successfully got response self.updateUI() } }
You can add observer for
(NS)NotificationCenter.- Register the observer with
(NS)NotificationCenterand then post the notification inside thecompletionHandlerofreverseGeocodeLocation. You can get more detail on this with this StackOverflow Post.
- Register the observer with
// I thing issue back ground thread you need to update your UI in main thread
var currentLatitude: Double!
var currentLongitude: Double!
var currentLocation: String!
var currentCity: String!
func getLocationName() {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)
geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
guard let addressDict = placemarks?[0].addressDictionary else {
return
}
if let city = addressDict["City"] as? String {
self.currentCity = city
print(city)
}
if let zip = addressDict["ZIP"] as? String {
print(zip)
}
if let country = addressDict["Country"] as? String {
print(country)
}
DispatchQueue.main.async {
self.nowUpdateUI()
// Update your UI in main thread
}
})
}
This entire piece of your code:
completionHandler: { placemarks, error in
guard let addressDict = placemarks?[0].addressDictionary else {
return
}
if let city = addressDict["City"] as? String {
self.currentCity = city
print(city)
}
if let zip = addressDict["ZIP"] as? String {
print(zip)
}
if let country = addressDict["Country"] as? String {
print(country)
}
self.nowUpdateUI()
}
)
is already happening in the completionHandler (which happens after everything is finished) Just also run your updateUI() inside the completionHandler. So your end code would be :
completionHandler: { placemarks, error in
guard let addressDict = placemarks?[0].addressDictionary else {
return
}
if let city = addressDict["City"] as? String {
self.currentCity = city
DispatchQueue.main.async {
updateUI()
}
}
if let zip = addressDict["ZIP"] as? String {
print(zip)
}
if let country = addressDict["Country"] as? String {
print(country)
}
self.nowUpdateUI()
}
)
The reason you have to use DispatchQueue.main is because your completionHandler is on a backgroundqueue but you MUST always do you UI related stuff from your mainQueue—so users get the fastest changing in their UI without any glitches. Imagine if you were doing on a background thread and it was happening slow
来源:https://stackoverflow.com/questions/42241177/how-to-call-a-func-within-a-closure