Onicha21
Onicha21

Reputation: 161

My main view controller is loaded by observing when a notification is posted and is being called multiple times

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

Answers (1)

Mojtaba Hosseini
Mojtaba Hosseini

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

Related Questions