Reputation: 161
I am starting my application from a loading screen that displays a gif while the users location and appropriate events are loaded in the background
I have attempted to call the deinit() method on the view controller to remove the observer as well as remove the view controller as a delegate to location updates to ensure the location update and subsequent launching of the main screen is only called once
My implementation may be rather complex but I will try to ensure that it is clear to follow. I call startApplication() from my AppDelegate file to launch the load screen
AppDelegate
func startApplication(animated: Bool, pushCategory: String?) {
let mainNC = self.window?.rootViewController as! UINavigationController
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let homeVC = storyboard.instantiateViewController(withIdentifier: String(describing: LoadViewController.self)) as! LoadViewController
if pushCategory != nil {
homeVC.pushCategory = pushCategory!
}
mainNC.pushViewController(homeVC, animated: false)
}
in my LoadVC I am loading the gif and then adding an observer for when local events have been retrieved from the database
override func viewDidLoad() {
super.viewDidLoad()
holdView.frame = UIScreen.main.bounds
holdView.loadGif(name: "ANO_loading_gif")
holdView.contentMode = .scaleAspectFill
showMessageView.layer.cornerRadius = 10.0
showMessageView.clipsToBounds = true
locationService.delegate = self
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
changeLocationMessage()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//get notified when events have been downloaded from API
NotificationCenter.default.addObserver(self, selector: #selector(receivedCloseEvents(_:)), name: NSNotification.Name(rawValue: Constants.Notifications.GET_EVENTS), object: nil)
}
Once the location has been found the LocationService method is called to make a REST API call to the server
func tracingLocation(currentLocation: CLLocation) {
GlobalService.sharedInstance().updateUserLocation(currentLocation)
}
Then in my 'GlobalService' file if the call was successful a series of functions are run in the background because of their time consuming nature
func updateUserLocation(_ UserLocation: CLLocation) {
g_userCLLocation = UserLocation
g_userCurrentLocation = nil
// get user location to update Hub name
for location in g_aryLocations {
let coreLocation = CLLocation(latitude: location.location_latitude!, longitude: location.location_longitude!)
let fDistance = coreLocation.distance(from: UserLocation)
// check event distance
if fDistance < Double(Constants.Numbers.EVENT_LIST_DISTANCE) {
g_userCurrentLocation = location
break
}
}
if g_userCLLocation != nil {
// get all valid events
WebService.sharedInstance().getActiveEvents(UserLatitude: UserLocation.coordinate.latitude, UserLongitude: UserLocation.coordinate.longitude)
{ (aryEvents, error) in
if let error = error {
//show issue with network & load from archives if accessible
} else {
//set events to the empty array
//move these time expensive tasks to background thread
self.group.enter()
//execute event recovery on background thread
DispatchQueue.global(qos: .background).async {
self.g_aryEvents = aryEvents!
self.getUsersWithinRange(aryEvents!)
self.updateUserEvent(nil)
}
}
}
}
}
UpdateUserEvent() finds the event the user is currently at through computation then once located it posts the notification to notify the LoadVC that it should move to the mainVC
//once user event has been updated send to
private func updateUserEvent(_ liveKey: Int?) {
//operations should happen on the background thread
//ensure live key is not nil
if liveKey != nil {
//clone events array
var swappableEventsArr = g_aryEvents
var eIndex = 0
//30 min start time
let bufferTime = Constants.Numbers.EVENT_LIVE_TIME
var potentialLiveEvent: EventObj?
//search for event with matching event ID to live key
for event in swappableEventsArr {
eIndex += 1
if event.event_id! == liveKey {
potentialLiveEvent = event
break
}
}
//set live event to true
guard let actualEvent = potentialLiveEvent else { return }
if actualEvent.event_time!.timeIntervalSinceNow <= Double(bufferTime) || actualEvent.event_user_id! == 1 {
actualEvent.event_is_live = true
}
//set former live event to false
if swappableEventsArr[0].event_id! != actualEvent.event_id! {
swappableEventsArr[0].event_is_live = false
}
//swap events index
swappableEventsArr.swapAt(0,eIndex - 1)
//remove all previous events
g_aryEvents.removeAll()
//set array to new order with live event in front
g_aryEvents = swappableEventsArr
}
//once value has been achieved exit thread
//set live event
self.g_aryEvents[0].event_is_live = true
self.g_objLiveEvent = self.g_aryEvents[0]
//this call can occur on the background thread
WebService.sharedInstance().updateUserEvent(UserEvent: g_objLiveEvent!.event_id!)
{ (result, error) in
if let error = error {
print(error)
} else {
print(result)
}
}
//move back to main thread
self.group.leave()
//notify main thread that events have finished loading
self.group.notify(queue: .main, execute: {
//call main VC immediately once done prcoessing event ordering
NotificationCenter.default.post(name: NSNotification.Name(rawValue: Constants.Notifications.GET_EVENTS), object: nil)
})
}
Finally, the selector method is called telling the LoadVC to move on to the MainVC
@objc func receivedCloseEvents(_ notification:Notification) {
locationService.stopUpdatingLocation()
let g_events = GlobalService.sharedInstance().g_aryEvents
self.events = g_events
//send events to Main VC once done
let mainVC = self.storyboard?.instantiateViewController(withIdentifier: String(describing: MainViewController.self)) as! MainViewController
mainVC.arrEvents = self.events
if self.pushCategory != nil {
mainVC.pushCategory = self.pushCategory!
}
self.navigationController?.pushViewController(mainVC, animated: true)
}
and then I call the deinit() method to remove the observer and unassign the LoadVC as the LocationService delegate
deinit {
//remove location service delegate
locationService.delegate = nil
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: Constants.Notifications.GET_EVENTS), object: nil)
}
Often times it behaves as expected however sometimes it will push the MainVC multiple times. I also put a property observer of g_usercllocation and in didSet it prints "location called" twice. I am unsure if it is because I am using requestLocation() for location for I thought that it was only to be called once when a location was found.
Upvotes: 0
Views: 511
Reputation: 119178
Two possible issues:
1- Observing more than once for the notification.
2- Posting more than once a single notification.
Since you need a single dispatch of your receivedCloseEvents
method, put the following line at the very beginning of the receivedCloseEvents
method.
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: Constants.Notifications.GET_EVENTS), object: nil)
So as soon as receiving the notification, it gets unregistered until the view appears again and reregister for the event.
Upvotes: 0