UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Do something in another class when the locationManagerDidChangeAuthorization method called

Forums > SwiftUI

In the following code, I have a LocationManager class which provides the city name of the current location via the @Published property wrapper lastSearchedCity.

Then I have a SearchManagerViewModel class that should be in charge of presenting the city name on SwiftUI views based on some conditions (not currently shown in the code below) via the @Published property wrapper cityName. It properly shows the city name when I call the searchAndSetCity() method from ContentView.swift inside an onAppear modifier.

My issue is that if the user turned Location Services off and turns it back On while he/she is in the ContentView.swift the Text view doesn't update, which is understandable since the searchAndSetCity() method would need to be called again.

How can I call the searchAndSetCity() method located inside the SearchManagerViewModel class every time the locationManagerDidChangeAuthorization(_ manager: CLLocationManager) method is called? I believed this method is called every time the authorization status changes.

LocationManager Class

final class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {

    private let locationManager = CLLocationManager()

    @Published var lastSearchedCity = ""

    var hasFoundOnePlacemark:Bool = false

    func checkIfLocationServicesIsEnabled(){
        DispatchQueue.global().async {
            if CLLocationManager.locationServicesEnabled(){
                self.locationManager.delegate = self
                self.locationManager.desiredAccuracy = kCLLocationAccuracyBest/// kCLLocationAccuracyBest is the default
                self.checkLocationAuthorization()
            }else{
                // show message: Services desabled!
            }
        }
    }

    private func checkLocationAuthorization(){
        switch locationManager.authorizationStatus{
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .restricted:
            // show message
        case .denied:
            // show message
        case .authorizedWhenInUse, .authorizedAlways:
            /// app is authorized
            locationManager.startUpdatingLocation()
        default:
            break
        }
    }
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        checkLocationAuthorization()
    }
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        hasFoundOnePlacemark = false

        CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)-> Void in
            if error != nil {
                self.locationManager.stopUpdatingLocation()
                // show error message
            } 
            if placemarks!.count > 0 {
                if !self.hasFoundOnePlacemark{
                    self.hasFoundOnePlacemark = true
                    let placemark = placemarks![0]

                    self.lastSearchedCity = placemark.locality ?? ""               
                }
                self.locationManager.stopUpdatingLocation()
            }else{
                // no places found
            }
        })
    }
}

SearchManagerViewModel Class

class SearchManagerViewModel: ObservableObject{
    @Published var cityName = "" // use this directly in SwifUI views

    @ObservedObject private var locationManager = LocationManager()

    // Call this directly fron onAppear in SwiftUI views
    // This method is more complex than what is shown here. It handles other things like HTTP requests etc.
    func searchAndSetCity(){
        locationManager.checkIfLocationServicesIsEnabled()
        self.cityName = locationManager.lastSearchedCity
    }
}

ContentView.swift

struct ContentView: View {

    @StateObject private var searchManager = SearchManagerViewModel()

    var body: some View {
        VStack {
            Text(searchManager.cityName)
                .font(.callout)
        }
        .onAppear{
            searchManager.searchAndSetCity()
        }
    }
}

1      

Do you have heard of the Delegate Pattern? Although, in SwiftUI land this is not very common on the surface, you need it when two classes have to communicate with each other and was very frequently used in UIKit.

Your SearchManagerViewModel and LocationManager need a reference to each other. So, in your case the LocationManager could be the SearchManagerViewModelDelegate with a weak reference to your SearchViewModel. Give it a try.

2      

@Hatsushira - Yes I have heard of the Delegate Pattern, it's an option but for some reason I was thinking that something like that could be done with Combine where you comunicate two classes. The Delegate Pattern, for some reason feels anti SwifUI.

The easiest would be to eliminate the SearchManagerViewModel and do everything from the LocationManager class but it would be doing other stuff not releated to locations and I don't really want to do that.

Thank you for your suggestion.

1      

@Hatsushira - Thank you for suggesting the Delegation Pattern, I ended up using it. Thanks.

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.