问题
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?
回答1:
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 on Protocol for 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
回答2:
// 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
}
})
}
回答3:
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