Learn Courses My Dashboard

Execute Function after long task

Hello community! I have a view with a task that takes a long time. At the time when the task finishes, the View might not be shown on the screen, so kind of is in the background.
I want to execute a function once this task is done. How can I do that? (most of the code is probably not relevant, just scroll down until you see the comment β†’ how to execute function here ←
I have tried using a background thread, but I am not even sure if this has to do with the problem. Any tips?

import SwiftUI
import PhotosUI

struct VideoPicker: UIViewControllerRepresentable{
    
    //    @Binding var videoURL:String?
    @Binding var videoURL2: URL?
    
    func makeUIViewController(context: Context) -> PHPickerViewController {
        
        var config = PHPickerConfiguration()
        config.filter = .videos
        let picker = PHPickerViewController(configuration: config)
        picker.delegate = context.coordinator
        return picker
    }
    
  

  func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator:NSObject, PHPickerViewControllerDelegate{
        
        
        
        let parent:VideoPicker
        init(_ parent: VideoPicker){
            
            self.parent = parent
        }
        
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            picker.dismiss(animated: true) {
                // do something on dismiss
            }
            
            guard let provider = results.first?.itemProvider else {return}
            provider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
                guard error == nil else{
                    print("error in video picker")
                    return
                }
                // receiving the video-local-URL / filepath
                guard let url = url else {
                    print("error 2 in video picker")
                    return
                }
            
              
                    print("original url \(url)")
                    let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)"
                    let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
                    try? FileManager.default.copyItem(at: url, to: newUrl)
                    //                self.parent.videoURL = newUrl.absoluteString



                    self.parent.videoURL2 = newUrl
                  // HOW TO EXECUTE FUNCTION HERE?
    
                
            }
        }
    }
}

struct ParentView: View {
@State var videoURL2 : URL?

var body: some View {
    
    VideoPicker(videoURL2: $videoURL2)
        .onChange(of: videoURL2) { newValue in
            //THIS IS HOW I EXECUTE THE FUNC AT THE MOMENT
            if videoURL2 != nil{
                function(videoURL: videoURL2!)
            } else {return}
        }
}

My current ,solution" only works if ParentView stays opened during the whole task. The line
self.parent.videoURL2 = newUrl gets executed either way tough, but the .onChange does not react.

My first guess is that you need to use a closure

2 Likes

Thank you! It took some time but I found a solution :slight_smile:

Can you share your solution? It may help others

of course:
I basically just used an ObservedObject variable, that is changed when the process is done. Then I save the video url to core data in those brackets : DispatchQueue.main.async {} because otherwise there is the violet kind of error. (changes from background thread not allowed). I included the whole code, although most of it is probably not relevant. So what purpose does the code serve? A video is added from the users library and saved in the Apps sandbox. This process of saving takes a long time, so the user might already see another view. I still have to save the videos url tough. Once this long process of copying it is done, I had to save the video’s location from the background, or at least make that possible. This is what the changes I have included here achieve. I am here for questions or suggestions for improvements

import SwiftUI
import PhotosUI

struct VideoPicker: UIViewControllerRepresentable{

//    @Binding var videoURL:String?
@Binding var videoURL2: URL?
@ObservedObject var exercise: Exercise
@Environment(\.managedObjectContext) private var viewContext

func makeUIViewController(context: Context) -> PHPickerViewController {
    
    var config = PHPickerConfiguration()
    config.filter = .videos
    let picker = PHPickerViewController(configuration: config)
    picker.delegate = context.coordinator
    return picker
}

func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

class Coordinator:NSObject, PHPickerViewControllerDelegate{
    
    
    
    let parent:VideoPicker
    init(_ parent: VideoPicker){
        
        self.parent = parent
    }
    
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        picker.dismiss(animated: true) {
            // do something on dismiss
        }
        
        guard let provider = results.first?.itemProvider else {return}
        provider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
            guard error == nil else{
                print("error in video picker")
                return
            }
            // receiving the video-local-URL / filepath
            guard let url = url else {
                print("error 2 in video picker")
                return
            }
            print("original url \(url)")
            // create a new filename
            let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)"
            // create new URL
            let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
            // copy item to APP Storage
            try? FileManager.default.copyItem(at: url, to: newUrl)
            //                self.parent.videoURL = newUrl.absoluteString
            self.parent.videoURL2 = newUrl
            
            
            
            
            
            
            let exercise = self.parent.exercise
            //HERE FUNC BEGINS
            
            
            
            let videoData = NSData(contentsOf: newUrl)
            
            // *** Get documents directory path *** //
            let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]
            
            // *** Append video file name *** //
            
            let fileName2 = UUID().uuidString
            
            let dataPath = paths.appending("/\(fileName2).mp4")
            
            if exercise.videoUrls != nil{
                exercise.videoUrls!.append("/\(fileName2).mp4")
                
                
            } else {exercise.videoUrls =  ["/\(fileName2).mp4"]}
            
            // *** Write video file data to path *** //
            videoData?.write(toFile: dataPath, atomically: false)
            
            //    PHPhotoLibrary.shared().performChanges({
            //        }
            
            DispatchQueue.main.async {
                try! self.parent.viewContext.save()
            }
            
        }
        
    }
}

}