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?
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)
}
}
}