Phontaine Judd
Phontaine Judd

Reputation: 438

Firebase storage image coming back blank

I know I'm missing something simple, but I've spent hours trying to figure it out. All my code works and runs fine except there is no image. It is blank. Why?

func loadMembers4(completion: @escaping ([User]) -> ()) {
    var usersArray = [User]()
    ref.child("members").observeSingleEvent(of: .value, with: { (snapshot) in
        for item in snapshot.children {
            if let snap = item as? FIRDataSnapshot {
                if let value = snap.value as? [String : Any] {
                    let birthday = value["birthday"] as! Int
                    let childParent = value["childParent"] as! String
                    let firstName = value["firstName"] as! String
                    let gender = value["gender"] as! String
                    let passcode = value["passcode"] as! Int
                    let profileImageUrl = value["profileImageUrl"] as! String

                    // get image

                    self.loadMemberProfilePict4(userProfileImageUrl: profileImageUrl, completion: { (userImage) in
                        let user = User(profilePhoto: userImage, userFirstName: firstName, userBirthday: birthday, userPasscode: passcode, userGender: gender, isUserChildOrParent: childParent)
                        usersArray.append(user)
                        usersArray.sort(by: {$0.birthday > $1.birthday})
                    })
                }
            }
        }
        completion(usersArray)
    })
}

func loadMemberProfilePict4(userProfileImageUrl: String, completion: @escaping (UIImage) -> ()) {
    var userImage = UIImage()
    let storageRef = FIRStorage.storage().reference(forURL: userProfileImageUrl)
    print(userProfileImageUrl)
    storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (imageData, error) in
        let profileImage = UIImage(data: imageData!)
        userImage = profileImage!
    })
    completion(userImage)
}

And the ViewDidLoad method:

loadMembers4 { (usersArray) in
        print(usersArray.count)
        self.currentUserName = usersArray[1].firstName
        self.instructionsLabel.text = "Choose daily and weekly job assignments for \(usersArray[1].firstName)."
        self.userImage.image = usersArray[1].photo
    }

I know the image is getting fetched from Firebase because when I print userProfileImageUrl in the loadMemberProfilePict4 function, it prints out the correct Firebase Storage location. But when I call it in the ViewDidLoad, the image shows up as blank. Nothing there.

Any ideas? Again, I'm sure it's something simple that I'm overlooking and just need a fresh pair of eyes to point it out. Thanks!

EDIT #1:

If I print out what's going on in the loadMembersProfilePict4 method, it shows the following:

func loadMemberProfilePict4(userProfileImageUrl: String, completion: @escaping (UIImage) -> ()) {
    var userImage = UIImage()
    let storageRef = FIRStorage.storage().reference(forURL: userProfileImageUrl)
    print("UserProfileURL: ",userProfileImageUrl)       // returns 4 strings of https://firebasestorage.google... (all correct)

    storageRef.data(withMaxSize: 1024 * 1024) { (imageData, error) in
        print("ImageData: ",imageData ?? "no data found!")          // returns 21061 bytes, 7668 bytes, 44887 bytes, 8995 bytes (is this right?)

        let profileImage = UIImage(data: imageData!)
        userImage = profileImage!
        print("UserImage: ",userImage.images ?? "no images found!")     // returns "no images found!"
    }
    completion(userImage)
}

So it would seem that somehow the image isn't getting loaded properly? I don't get it. I use this exact same code in another part of my project, and it loads the user images just fine. What is going on? Aargh! So frustrating.

Upvotes: 1

Views: 1135

Answers (3)

Nishant Bhindi
Nishant Bhindi

Reputation: 2252

I simply put two line for userName and image of viewDidLoad() method into completion block of respective method. Remove that lines from viewDidLoad().

func loadMemberProfilePict4(userProfileImageUrl: String, completion: @escaping (UIImage) -> ()) {
    var userImage = UIImage()
    let storageRef = FIRStorage.storage().reference(forURL: userProfileImageUrl)
    print(userProfileImageUrl)
    storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (imageData, error) in
        let profileImage = UIImage(data: imageData!)
        userImage = profileImage!
        self.userImage.image = profileImage
    })
    completion(userImage)
}

For Username put other line in completion block of loadMembers4()

    func loadMembers4(completion: @escaping ([User]) -> ()) {
    var usersArray = [User]()
    ref.child("members").observeSingleEvent(of: .value, with: { (snapshot) in
        for item in snapshot.children {
            if let snap = item as? FIRDataSnapshot {
                if let value = snap.value as? [String : Any] {
                    let birthday = value["birthday"] as! Int
                    let childParent = value["childParent"] as! String
                    let firstName = value["firstName"] as! String
                    let gender = value["gender"] as! String
                    let passcode = value["passcode"] as! Int
                    let profileImageUrl = value["profileImageUrl"] as! String

                    // get image

                    self.loadMemberProfilePict4(userProfileImageUrl: profileImageUrl, completion: { (userImage) in
                        let user = User(profilePhoto: userImage, userFirstName: firstName, userBirthday: birthday, userPasscode: passcode, userGender: gender, isUserChildOrParent: childParent)
                        **self.currentUserName = user.firstName**
                        self.instructionsLabel.text = "Choose daily and weekly job assignments for \(user.firstName)."
                        usersArray.append(user)
                        usersArray.sort(by: {$0.birthday > $1.birthday})
                    })
                }
            }
        }
        completion(usersArray)
    })
}

Upvotes: 2

Phontaine Judd
Phontaine Judd

Reputation: 438

So after spending the past two days trying to figure out a solution, I've come to the conclusion that for some reason Swift won't allow me to make embedded @escaping closures. I can create separate @escaping closures and they both work, but if I put one inside another, the second one won't execute properly.

So this is what I ended up doing:

Function #1: gets Firebase data for users, including a URL string for users' photo stored on Firebase Storage.

func loadMembers(completion: @escaping ([UserClass]) -> ()) {
    var usersArray = [UserClass]()
    ref.child("members").observeSingleEvent(of: .value, with: { (snapshot) in
        for item in snapshot.children {
            if let snap = item as? FIRDataSnapshot {
                if let value = snap.value as? [String : Any] {
                    let birthday = value["birthday"] as! Int
                    let childParent = value["childParent"] as! String
                    let firstName = value["firstName"] as! String
                    let gender = value["gender"] as! String
                    let passcode = value["passcode"] as! Int
                    let profileImageUrl = value["profileImageUrl"] as! String

                    let user = UserClass(userProfileImageURL: profileImageUrl, userFirstName: firstName, userBirthday: birthday, userPasscode: passcode, userGender: gender, isUserChildOrParent: childParent)
                    usersArray.append(user)
                    usersArray.sort(by: {$0.birthday > $1.birthday})
                }
            }
        }
        completion(usersArray)
    })
}

Function #2: takes URL string from first function and uses it to load users' images from Firebase. This does not give me the complete array of images, however, as it loads them incrementally one at a time. Instead of giving me count: 4, it would give me count: 1, count: 2, count: 3, count: 4.

So when I would load the view controller, the images would cycle through each user before landing on the correct one. Not ideal.

// WORKS BEST, BUT IMAGES CYCLE THRU ALL USERS
func loadMemberProfilePict(userImageURL: String, userFirstName: String, userBirthday: Int, userPasscode: Int, userGender: String, userChildParent: String, completion: @escaping ([User]) -> ()) {
    let storageRef = FIRStorage.storage().reference(forURL: userImageURL)
    storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
        let pic = UIImage(data: data!)
        let user = User(profilePhoto: pic!,
                        userFirstName: userFirstName,
                        userBirthday: userBirthday,
                        userPasscode: userPasscode,
                        userGender: userGender,
                        isUserChildOrParent: userChildParent)
        self.users.append(user)
        self.users.sort(by: {$0.birthday > $1.birthday})
        completion(self.users)
    })
}

Function #3: which leads to this function where I used a simple 'if' statement to wait until all the users had been loaded before displaying them on the view.

In ViewDidLoad:

loadMembers { (usersArray) in
        var memberImageCount = 0

        for user in usersArray {
            self.loadMemberProfilePict(userImageURL: user.imageURL, userFirstName: user.firstName, userBirthday: user.birthday, userPasscode: user.passcode, userGender: user.gender, userChildParent: user.childParent, completion: { (usersIntermediateArray) in
                memberImageCount += 1
                if memberImageCount == usersArray.count {
                    self.users = usersIntermediateArray
                    self.userImage.image = self.users[3].photo
                    self.currentUserName = self.users[3].firstName
                    self.instructionsLabel.text = "Choose daily and weekly job assignments for \(self.users[3].firstName)."
                    self.navigationItem.title = self.users[3].firstName
                }
            })
        }
    }

I don't know if this is the best practice, but it's functional, and I hope it helps someone in the future who might have the same problem.

Upvotes: 0

Mike McDonald
Mike McDonald

Reputation: 15963

This is why your code is blank:

// #1
var userImage = UIImage()
// #2
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (imageData, error) in
    // #4
    let profileImage = UIImage(data: imageData!)
    userImage = profileImage!
})
// #3
completion(userImage)

There's a race condition because downloading the data is asynchronous. This means you return the empty image before the image has been downloaded. Fixing it by doing:

// #1
var userImage = UIImage()
// #2
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (imageData, error) in
    // #3
    completion(UIImage(data: imageData!))
})

Should solve the problem.

Upvotes: 0

Related Questions