Ofri
Ofri

Reputation: 289

Returning UIImage Async using Task

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

Answers (1)

John Snow
John Snow

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

Related Questions