Home > other >  delaying function until separate function has ended
delaying function until separate function has ended

Time:12-25

After having asked this question it turned out I was not specific enough, to the point where the question and its answer did actually not solve my issue. In short, I have a function that is called by a CLLocationManager and takes a while to update things on the server. In the UI a function fetch() can be triggered. This should run after the update() has finished. Thus, if there is no update() running, right away.

Here is a MRE:

import SwiftUI
import CoreLocation

class ExampleManager: ObservableObject {
    func fetch() {
        print("these would be some results")
    }
}

struct ContentView2: View {
    
    @StateObject var locationManager = LocationManager()
    @StateObject var example = ExampleManager()
    
    var body: some View {
        Button {
            example.fetch()
        } label: {
            Text("fetch")
        }
    }
}

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    // .. necessary stuff of CLLocationManager
    
    private func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) async {
        guard let newLocation: CLLocation = locations.first else { return }
        await update(newLocation: newLocation)
    }
    
    func update(newLocation: CLLocation) async {
        print("1")
        try! await Task.sleep(nanoseconds: 10_000_000_000)
        print("2")
    }
}

CodePudding user response:

You probably don't want to show the button unless there is something to fetch to begin with. I have a couple of scenarios here:

import UIKit
import SwiftUI
import CoreLocation

var greeting = "Hello, playground"

class ExampleManager: ObservableObject {
    func fetch() {
        
    }
}

struct ContentView: View {
    @StateObject var locationManager = LocationManager()
    @StateObject var example = ExampleManager()
    
    var body: some View {
//        ProgressView()
//            .onReceive(locationManager.$updated) { updated in
//                example.fetch()
//            }
        if locationManager.updated {
            Button {
                example.fetch()
            } label: {
                Text("fetch")
            }
        } else {
            EmptyView()
        }
    }
}

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    @Published var updated = false
    
    private func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) async {
        guard let newLocation: CLLocation = locations.first else { return }
        await update(newLocation: newLocation)
    }
    
    func update(newLocation: CLLocation) async {
        print("1")
        try! await Task.sleep(nanoseconds: 10_000_000_000)
        print("2")
        updated = true
    }
}

CodePudding user response:

You should convert what you are calling now Managers into services.

//Make a service
class ExampleService {
    func fetch() {
        print("3 these would be some results")
    }
}

Then add a var onReceive: ((CLLocation) async -> Void)? that get called after your update.

//Change to service
class LocationService: NSObject, CLLocationManagerDelegate {
    var count: Int = 0
    private let mgr = CLLocationManager()
    /// gets called @didUpdateLocations after update
    var onReceive: ((CLLocation) async -> Void)?
    override init() {
        super.init()
        //Mimics location remove for real code
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] timer in
            count  = 1
            
            Task{
                await locationManager(mgr, didUpdateLocations: [.init(latitude: Double((-90...90).randomElement()!), longitude: Double((-180...180).randomElement()!))])
            }
            if count >= 10{
                timer.invalidate()
            }
        }
    }
    
    private func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) async {
        guard let newLocation: CLLocation = locations.first else { return }
        await update(newLocation: newLocation)
        //Run this after update
        await onReceive?(newLocation)
        print("4 done")
    }
    
    func update(newLocation: CLLocation) async {
        print("1")
        try! await Task.sleep(nanoseconds: 10_000_000_000)
        print("2")
    }
}

Both of these services wold come together in a Manager

struct ExampleManager{
    let exampleSvc: ExampleService = .init()
    let locationSvc: LocationService = .init()
    
    init(){
        //Give onReceive a value
        locationSvc.onReceive = { [self] location in
            //Call fetch when this closure is called
            exampleSvc.fetch()
        }
    }
    
    func fetch(){
        exampleSvc.fetch()
    }
}

Then your View will be simplified and unconcerned about anything that happens that doesn't directly involve UI.

struct ContentView2: View {
    
    @State var examapleMgr = ExampleManager()
    
    var body: some View {
        Button {
            examapleMgr.fetch()
        } label: {
            Text("fetch")
        }
    }
}

You can add variable to lock the Button until update has run at least once.

@MainActor
class ExampleManager: ObservableObject{
    let exampleSvc: ExampleService = .init()
    let locationSvc: LocationService = .init()
    //You can add a check if you want to disable the button if you dont want to run fetch until location has updated at least once
    @Published var hasRunOnce: Bool = false
    init(){
        //Give onReceive a value
        locationSvc.onReceive = { [self] location in
            hasRunOnce = true
            //Call fetch when this closure is called
            fetch()
        }
    }
    
    func fetch(){
        exampleSvc.fetch()
    }
}
struct ContentView2: View {
    
    @StateObject var examapleMgr = ExampleManager()
    
    var body: some View {
        Button {
            examapleMgr.fetch()
        } label: {
            Text("fetch")
        }.disabled(!examapleMgr.hasRunOnce)
    }
}
  • Related