iosswiftdocumentsdirectory

Updating a bool variable after saving image to documents directory doesn’t work as expected in Swift


I am working on the user profile page of my app. I've got a global bool variable (updateProfile) that is set to false by default. When the user makes any change to their profile info, like change/remove their profile picture, the database is updated and the image is downloaded and saved in the documents directory. Here is the code where that image is saved after downloading:

struct Downloads {

    // Create a static variable to start the download after tapping on the done button in the editUserProfile page
    static var updateProfile: Bool = false

    static func downloadUserProfilePic() {

        Database.database().reference().child("Users").child(userID!).child("Profile Picture URL").observeSingleEvent(of: .value) { (snapshot) in
        guard let profilePictureString = snapshot.value as? String else { return }
        guard let profilePicURL = URL(string: profilePictureString) else { return }

        let session = URLSession(configuration: .default)

        let downloadPicTask = session.dataTask(with: profilePicURL) {
                (data, response, error) in

            if let e = error {
                print("error downloading with error: \(e)")

            } else {
                if let res = response as? HTTPURLResponse {
                    Downloads.imageCached = true // The image has been downloaded
                    print("Downloaded with \(res.statusCode)")

                    if let imageData = data {
                        let image = UIImage(data: imageData)

                        // Now save the image in the documents directory
                        // Get the url of the documents directory
                        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
                        // Name your image with the user ID to make it unique
                        let imageName = userID! + "profilePic.jpg"
                        // Create the destination file URL to save the image
                        let imageURL = documentsDirectory.appendingPathComponent(imageName)
                        print(imageURL)
                        let data = image?.jpegData(compressionQuality: 0.5)

                        do {
                            // Save the downloaded image to the documents directory

                            // Write the image data to disk
                            try data!.write(to: imageURL)
                            print("Image Saved")
                            updateProfile = true

                        } catch {
                            print("Error saving file \(error)")
                        }


                    } else {
                        print("Couldnt get image")
                    }

                } else {
                    print("Couldnt't get response")
                }
            }
        }
        downloadPicTask.resume()        

    }
}

SomeOtherViewController

// When the user taps on the 'done' button
@objc func doneButtonTapped() {

    uploadUserSelectedPicture()

}

func uploadUserSelectedPicture() {

    // Download the profile picture and save it
    Downloads.downloadUserProfilePic()

    if Downloads.updateProfile == true {
        // Go to the user profile page
        let userProfilePage = UserProfilePage()
        self.present(userProfilePage, animated: true)
    }

}

As you can see, I am printing "Image saved" as soon as the image is saved to the documents directory and also the updateProfile global variable is changed to true. And on SomeOtherViewController, a page is presented (when tapped on the done button) only if the updateProfile variable is true (Which means the image should be saved to the documents directory).

But the only problem is, the variable is set to true before the image is saved, how do I know this? I know this because the page is presented before the execution of the print statement print("Image Saved"). Why does this happen? Is there any way I could get rid of this problem?


Solution

  • Actually your code is supposed to never present the page. But as you forgot to set updateProfile explicitly to false before calling downloadUserProfilePic the page is presented without the image from the second call on.

    Nevertheless both observe and dataTask work asynchronously. You have to add a completion handler.

    Simple form:

    static var updateProfile: Bool = false  
    

    static func downloadUserProfilePic(completion: @escaping () -> Void) {
    
    ...
    
        do {
           // Save the downloaded image to the documents directory
    
           // Write the image data to disk
           try data!.write(to: imageURL)
           print("Image Saved")          
           completion()
        }
    
    ...
    

    func uploadUserSelectedPicture() {
    
        // Download the profile picture and save it
        Downloads.downloadUserProfilePic { [weak self] in
            // Go to the user profile page
            DispatchQueue.main.async {
               let userProfilePage = UserProfilePage()
               self?.present(userProfilePage, animated: true)
            }
        }
    
    }