Reputation: 438
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
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
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
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