Search code examples
iosswiftfirebaseios-multithreading

ViewDidAppear Not updating my picture from Firebase but update text info


I am still very new to IOS programming, and I've run into an issue while using the Firebase database and storage. Here is the image:

Before Editing File:

This is what the profile looks like before editing it. If the user hit the edit button, we will enter the editing mode.

During Editing

During editing, we change its name, description, person-icon, and banner picture, and when we finish it, it will go back to this profile and see the updated file. However, After editing, what it returns is this:

after editing

The name and description did change, but the profile picture did not change. In fact, if I edit the profile again, it will update to the last picture I've changed. If I quit and return to the profile page, it will show the correct picture, which is the last updated picture. Here is my code in the profile view controller:

override func viewDidLoad() {
    super.viewDidLoad()
    print("The center of phone is: \(view.center.x) + \(view.center.y)")
    print("The center of pic is: \(PersonIcon.center.x) + \(Banner.center.y)")
    UserDataManager.getUserInfo(uid: user!.uid) { (userData) in
        self.ProfileName.text = userData["UserName"]!
        if (userData["UserBio"] != "NULL") {
            self.ProfileBio.text = userData["UserBio"]!
        }
        if (userData["UserIconURL"] != "NULL") {
            let url = URL(string: userData["UserIconURL"]!)
            let task = URLSession.shared.dataTask(with: url!, completionHandler: { (data, _, error) in
                guard let data = data, error == nil else {
                    print("Error trying to download user icon")
                    return
                }
                
                DispatchQueue.main.async {
                    let image = UIImage(data: data)
                    self.PersonIcon.image = image
                    print("Should be Updated?")
                }
            })
            task.resume()
        }
        if (userData["UserBannerURL"] != "NULL") {
            let url = URL(string: userData["UserBannerURL"]!)
            let task = URLSession.shared.dataTask(with: url!, completionHandler: { (data, _, error) in
                guard let data = data, error == nil else {
                    print("Error trying to download user Banner")
                    return
                }
                
                DispatchQueue.main.async {
                    let image = UIImage(data: data)
                    self.Banner.image = image
                    print("Should be Updated?")
                }
            })
            task.resume()
        }
    }
    makeRounded(ProfileImage: PersonIcon)

    // Do any additional setup after loading the view.
}

And the function ViewDidAppear is the exact same code(except for the super.viewDidAppear(animated)). I feel like this has something to do with threading, as I did async the download thread with the main thread to ensure the profile gets updated, but I'm still new to threading in IOS and can't tell if that is the problem here. Please help, thank you!

The code for editing user's info:

@IBAction func onSubmit(_ sender: Any) {
    // Todo: Submit the data to server and update user info
    let DataManager = FirebaseDataAccessManager()
    var url_id = "Nothing"
    
    // To make sure we get uid before we upload, all the code regarding upload must be put inside this closure
    DataManager.getUserInfo(uid: user!.uid) { (userData) in
        url_id = userData["URL_ID"]!
        DataManager.updateUserIcon(URL_ID: url_id, image: self.PersonIcon.image!) { (success, urlString) in
            if (success) {
                print("PersonIcon Upload Success!")
                print("URL stored into the data is: \(urlString)")
                DataManager.updateUserIconURL(uid: user!.uid, url: urlString)
            }
        }
        DataManager.updateUserBanner(URL_ID: url_id, image: self.Banner.image!) { (success, urlString) in
            if (success) {
                print("PersonBanner Upload Success!")
                print("URL stored into the data is: \(urlString)")
            }
            DataManager.updateUserBannerURL(uid: user!.uid, url: urlString)
        }
    }
    
    DataManager.updateUserSetting(uid: user!.uid, UserName: self.PersonName.text as! String, UserBio: self.PersonBio.text as! String, UserKeyword: self.PersonKeyword.text as! String) { (success) in
        if (success) {
            self.navigationController?.popViewController(animated: true)
        }
        else {
            print("We're not going anywhere cuz update failed!")
        }
    }
    

    self.navigationController?.popViewController(animated: true)
}

Let me know if you also need to code in DataManager. It's a helper class containing calls of Firebase Database API.

The code for viewDidAppear():

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        UserDataManager.getUserInfo(uid: user!.uid) { (userData) in
            self.ProfileName.text = userData["UserName"]!
            if (userData["UserBio"] != "NULL") {
                self.ProfileBio.text = userData["UserBio"]!
            }
            if (userData["UserIconURL"] != "NULL") {
                let url = URL(string: userData["UserIconURL"]!)
                let task = URLSession.shared.dataTask(with: url!, completionHandler: { (data, _, error) in
                    guard let data = data, error == nil else {
                        print("Error trying to download user icon")
                        return
                    }
                    
                    DispatchQueue.main.async {
                        let image = UIImage(data: data)
                        self.PersonIcon.image = image
                    }
                })
                task.resume()
            }
            if (userData["UserBannerURL"] != "NULL") {
                let url = URL(string: userData["UserBannerURL"]!)
                let task = URLSession.shared.dataTask(with: url!, completionHandler: { (data, _, error) in
                    guard let data = data, error == nil else {
                        print("Error trying to download user Banner")
                        return
                    }
                    
                    DispatchQueue.main.async {
                        let image = UIImage(data: data)
                        self.Banner.image = image
                    }
                })
                task.resume()
            }
        }

        makeRounded(ProfileImage: PersonIcon)
    }

Update: Thanks to @Pratik Prajapati, the problem is that I refreshed the page too soon before the picture got to update, so I've re-write my code into this, and it worked!

@IBAction func onSubmit(_ sender: Any) {
        // Todo: Submit the data to server and update user info
        let DataManager = FirebaseDataAccessManager()
        var url_id = "Nothing"
        
        DataManager.updateUserSetting(uid: user!.uid, UserName: self.PersonName.text!, UserBio: self.PersonBio.text as! String, UserKeyword: self.PersonKeyword.text as! String) { (success) in
            if (success) {
                DataManager.getUserInfo(uid: user!.uid) { (userData) in
                    url_id = userData["URL_ID"]!
                    DataManager.updateUserIcon(URL_ID: url_id, image: self.PersonIcon.image!) { (success, urlString) in
                        if (success) {
                            print("PersonIcon Upload Success!")
                            print("URL stored into the data is: \(urlString)")
                            DataManager.updateUserIconURL(uid: user!.uid, url: urlString)
                            
                            DataManager.updateUserBanner(URL_ID: url_id, image: self.Banner.image!) { (success, urlString) in
                                if (success) {
                                    print("PersonBanner Upload Success!")
                                    print("URL stored into the data is: \(urlString)")
                                    self.navigationController?.popViewController(animated: true)
                                }
                                DataManager.updateUserBannerURL(uid: user!.uid, url: urlString)
                            }
                        }
                    }
                }
            }
            else {
                print("We're not going anywhere cuz update failed!")
            }
        }
        // To make sure we get uid before we upload, all the code regarding upload must be put inside this closure
    }

However, I do believe this is not the right way to do it. I've literally chained 3 completion blocks together, and the upload literally will take a second before I can switch back to profile from editing. I've definitely done something wrong. Any advice on how can I improve this code? What if I have 10 info instead of 3 to update? I can't chain 10 completion blocks together!

Regardless, thanks for the info guys. I did learn a lot!


Solution

  • Thanks to @Pratik Prajapati, the problem is that I refreshed the page too soon before the picture got to update, so I've re-write my code into this, and it worked!

    @IBAction func onSubmit(_ sender: Any) {
        // Todo: Submit the data to server and update user info
        let DataManager = FirebaseDataAccessManager()
        var url_id = "Nothing"
        
        DataManager.updateUserSetting(uid: user!.uid, UserName: self.PersonName.text!, UserBio: self.PersonBio.text as! String, UserKeyword: self.PersonKeyword.text as! String) { (success) in
            if (success) {
                DataManager.getUserInfo(uid: user!.uid) { (userData) in
                    url_id = userData["URL_ID"]!
                    DataManager.updateUserIcon(URL_ID: url_id, image: self.PersonIcon.image!) { (success, urlString) in
                        if (success) {
                            print("PersonIcon Upload Success!")
                            print("URL stored into the data is: \(urlString)")
                            DataManager.updateUserIconURL(uid: user!.uid, url: urlString)
                            
                            DataManager.updateUserBanner(URL_ID: url_id, image: self.Banner.image!) { (success, urlString) in
                                if (success) {
                                    print("PersonBanner Upload Success!")
                                    print("URL stored into the data is: \(urlString)")
                                    self.navigationController?.popViewController(animated: true)
                                }
                                DataManager.updateUserBannerURL(uid: user!.uid, url: urlString)
                            }
                        }
                    }
                }
            }
            else {
                print("We're not going anywhere cuz update failed!")
            }
        }
        // To make sure we get uid before we upload, all the code regarding upload must be put inside this closure
    }
    

    Obviously, this can be written in a better format using dispatchgroup, but this works for me now.