zlyt
zlyt

Reputation: 269

How to call function after multiple asynchronous api requests finish in SwiftUI?

I'm still building my skills in SwiftUI, so thank you to any experienced developers that can help answer my question.

For context, I am building an app that displays markers on a map, and also a list of the markers as well. Im using an @EnvironmentObject to hold the data so that there is ‘one source of truth’ and so that the map and list automatically update when the data changes.

To get the data I need I make 2 types of api requests. One to get a list of locations, then for each location I make a request for extra information about each respective location.

In order to update the @EnvironmentObject I have setup though, I need to complete all requests then update it from the main thread of the application. So my issue is that I need to be able to call a function only after the api requests finish.

The code below shows how I implement api requests in my app at the moment, but Im hoping someone can show me how to change them so that I can call a function after the 2 requests complete:


Here is a skeleton of my file that displays a map & where I make my api requests: The main part is in the ‘extension GMController’ at the bottom

import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Foundation



struct GoogMapView: View {
    var body: some View {
        GoogMapControllerRepresentable()
    }
}



class GoogMapController: UIViewController, CLLocationManagerDelegate {
    var locationManager = CLLocationManager()
    var mapView: GMSMapView!
    let defaultLocation = CLLocation(latitude: 42.361145, longitude: -71.057083)
    var zoomLevel: Float = 15.0
    let marker : GMSMarker = GMSMarker()


    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestAlwaysAuthorization()
        locationManager.distanceFilter = 50
        locationManager.startUpdatingLocation()
        locationManager.delegate = self

        let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude, longitude: defaultLocation.coordinate.longitude, zoom: zoomLevel)
        mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setMinZoom(14, maxZoom: 20)
        mapView.settings.compassButton = true
        mapView.isMyLocationEnabled = true
        mapView.settings.myLocationButton = true
        mapView.settings.scrollGestures = true
        mapView.settings.zoomGestures = true
        mapView.settings.rotateGestures = true
        mapView.settings.tiltGestures = true
        mapView.isIndoorEnabled = false


        marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
        marker.title = "Boston"
        marker.snippet = "USA"
        marker.map = mapView

        view.addSubview(mapView)

    }

    // Handle incoming location events.
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
      let location: CLLocation = locations.last!
      print("Location: \(location)")

      let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)


        mapView.animate(to: camera)

    }

    // Handle authorization for the location manager.
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
      switch status {
      case .restricted:
        print("Location access was restricted.")
      case .denied:
        print("User denied access to location.")
        // Display the map using the default location.
        mapView.isHidden = false
      case .notDetermined:
        print("Location status not determined.")
      case .authorizedAlways: fallthrough
      case .authorizedWhenInUse:
        print("Location status is OK.")
      }
    }

    // Handle location manager errors.
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
      locationManager.stopUpdatingLocation()
      print("Error: \(error)")
    }

}


// UIViewControllerExtension
extension GMController {


    func requestAndCombineGData(location: CLLocation, radius: Int) {
    // Clears map of markers
        self.mapView.clear()

    // vvv  THIS IS WHERE I AM TRYING TO CALL THE REQUESTS IN SUCCESSION AND THEN CALL A FUNCTION AFTER THE REQUESTS ARE FINISHED  vvv
        // Calls 'Nearby Search' request
        googleClient.getGooglePlacesData(location: location, withinMeters: radius) { (response) in
            print("Made Nearby Search request. Returned response here:", response)

            // loops through each result from the above Nearby Request' to get the 'place_id' and make 'Place Details'
            for location in response.results {
                // Calls 'Place Details' request
                self.googleClient.getGooglePlacesDetailsData(place_id: location.place_id) { (detailsResponse) in
                    print("GMV returned - detailsResponse.result - ", detailsResponse.result)

                }
            }            
        }

    }

}



struct GoogMapControllerRepresentable: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<GMControllerRepresentable>) -> GMController {
        return GMController()
    }

    func updateUIViewController(_ uiViewController: GMController, context: UIViewControllerRepresentableContext<GMControllerRepresentable>) {

    }
}



Does anyone know how I can alter my code so that I am able to call a function only after all the api requests complete that is able to use the data from the requests?

Upvotes: 0

Views: 804

Answers (1)

Zyfe3r
Zyfe3r

Reputation: 663

Assuming that you are calling your second API call from the first or when first API call ends.

Add an observer on your viewDidLoad()

NotificationCenter.default.addObserver(self, selector: #selector(
self.methodOfReceivedNotification(notification:)), name: Notification.Name
("NotificationIdentifier"), object: nil)

then add post notification on the 'location' api call when a case is met or when a condition is satisfied or when call is finished

NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"),
object: nil)

Then inside this, write whatever you want to happen after the location call is finished.

@objc func methodOfReceivedNotification(notification: Notification) 
{
///
}

Upvotes: 1

Related Questions