Reputation: 289
I have a UICollectionView of cells each including an image. I want to load the cell's images.
My code that instantiate the image :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "product_collection_cell", for: indexPath) as! ProductsCollectionViewCell
let prodInCell = searchActive ? filtered[indexPath.row] : products[indexPath.row]
// Set fields
cell.ProductImageView.image = prodInCell.GetProductImage()
cell.ProductName.text = prodInCell.Name()
cell.ProductPrice.text = String(prodInCell.Price())
cell.productUniqueID = prodInCell.UniqueID()
return cell
}
My Product's GetProductImage function:
public func GetProductImage() -> UIImage
{
let prodID = self.UniqueID()
let dbRef = Storage.storage().reference().child(prodID).child("pic0.jpg")
var prodImg = #imageLiteral(resourceName: "DefaultProductImage")
let imgTask = dbRef.getData(maxSize: 10*1024*1024, completion: // Up to 10 MB pictures
{
(data, error) in
if let data = data
{
if let img = UIImage(data: data)
{
prodImg = img
}
}
})
imgTask.observe(.progress, handler: {(snapshot) in
print (snapshot.progress ?? "NO MORE PROGRESS")
})
imgTask.resume()
return prodImg
}
I want a UIImage to be retrieved from Firebase Storage, or return a DefaultProductImage if none exists. Current implementation stucks my UI and seems to not really load anything from Firebase.
How do I Make this work ? I would also like for it to not take so much time - so perhaps using a couple of tasks to each load an image would be a good solution.
Edit :
This is my code now :
accept You can use a completion block to return the UIImage asynchronously.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "product_collection_cell", for: indexPath) as! ProductsCollectionViewCell
let prodInCell = searchActive ? filtered[indexPath.row] : products[indexPath.row]
// Set fields
cell.ProductImageView.image = #imageLiteral(resourceName: "DefaultProductImage")
prodInCell.GetProductImage() { image in
cell.ProductImageView.image = image
}
cell.ProductName.text = prodInCell.Name()
cell.ProductPrice.text = String(prodInCell.Price())
cell.productUniqueID = prodInCell.UniqueID()
return cell
}
public func GetProductImage(completion: ((UIImage?) -> Void)) {
let prodID = self.UniqueID()
let dbRef = Storage.storage().reference().child(prodID).child("pic0.jpg")
let imgTask = dbRef.getData(maxSize: 10*1024*1024, completion: { (data, error) in
if let data = data, let img = UIImage(data: data) {
completion(img)
} else {
completion(nil)
}
})
imgTask.observe(.progress, handler: {(snapshot) in
print (snapshot.progress ?? "NO MORE PROGRESS")
})
imgTask.resume()
}
And now I get
Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
In function
- (void)invokeFetchCallbacksOnCallbackQueueWithData:(GTM_NULLABLE NSData *)data
error:(GTM_NULLABLE NSError *)error {
// Callbacks will be released in the method stopFetchReleasingCallbacks:
GTMSessionFetcherCompletionHandler handler;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
handler = _completionHandler;
if (handler) {
[self invokeOnCallbackQueueUnlessStopped:^{
handler(data, error);
// Post a notification, primarily to allow code to collect responses for
// testing.
//
// The observing code is not likely on the fetcher's callback
// queue, so this posts explicitly to the main queue.
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
if (data) {
userInfo[kGTMSessionFetcherCompletionDataKey] = data;
}
if (error) {
userInfo[kGTMSessionFetcherCompletionErrorKey] = error;
}
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherCompletionInvokedNotification
userInfo:userInfo
requireAsync:NO];
}];
}
} // @synchronized(self)
In line handler(data,error);
With error error NSError * domain: @"com.google.HTTPStatus" - code: 404
Upvotes: 1
Views: 222
Reputation: 462
You can use a completion block to return the UIImage asynchronously.
E.g. you could update your code to the following:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "product_collection_cell", for: indexPath) as! ProductsCollectionViewCell
let prodInCell = searchActive ? filtered[indexPath.row] : products[indexPath.row]
// Set fields
cell.ProductImageView.image = #imageLiteral(resourceName: "DefaultProductImage")
prodInCell.GetProductImage() { image in
cell.ProductImageView.image = image
}
cell.ProductName.text = prodInCell.Name()
cell.ProductPrice.text = String(prodInCell.Price())
cell.productUniqueID = prodInCell.UniqueID()
return cell
}
And:
public func GetProductImage(completion: ((UIImage?) -> Void)) {
let prodID = self.UniqueID()
let dbRef = Storage.storage().reference().child(prodID).child("pic0.jpg")
let imgTask = dbRef.getData(maxSize: 10*1024*1024, completion: { (data, error) in
if let data = data, let img = UIImage(data: data) {
completion(img)
} else {
completion(nil)
}
})
imgTask.observe(.progress, handler: {(snapshot) in
print (snapshot.progress ?? "NO MORE PROGRESS")
})
imgTask.resume()
}
Upvotes: 1