chiahsien / CHTCollectionViewWaterfallLayout

The waterfall (i.e., Pinterest-like) layout for UICollectionView.
MIT License
4.52k stars 699 forks source link

How should async downloading images work with waterfall's cgsize? #138

Open rlam3 opened 8 years ago

rlam3 commented 8 years ago

How should async downloading images work with waterfall's cgsize?

Should we be doing async downloads inside cgsize ?

chiahsien commented 8 years ago

Should we be doing async downloads inside cgsize ?

I don't understand your question. Your server should provide images information to you, includes images size. Does it answer your question?

rlam3 commented 8 years ago

This requires there to be a size that needs to be returned correct?

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

So prior to the images being loaded the size must be returned for the cell. And in order to obtain the size, we must first asynchronously download the image to obtain the size to be returned here.

My question is ... how should we be handling the async download of images to obtain size to later be returned by CGSize. And because it is async, how should the completion handling look like within that particular code block. Ex. I'm using alamofireimage to obtain the image, but prior to alamofireimage finish obtaining the image, it will return nil because alamofire is async.

chiahsien commented 8 years ago

I assume that you get the image URLs from your server. If so, then your server can send image size and URL information together.

If your server don't know image size… sorry but I don't think there is a good way to solve the problem.

rlam3 commented 8 years ago

@chiahsien When you say the server should be sending the image size. In my particular case, I'm using AWS S3 to host the images. Correct me if I'm wrong, you're saying that I should be doing a full json request with s3 url in an external api to get the size every time it loads each cell?

chiahsien commented 8 years ago

No, what I'm saying is something like this:

  1. You send an API call to your server to get image information.
  2. Server responses a JSON like this
[
    {
        ImageURL: "http://xxx.xxx/abc.jpg",
        width: 128,
        height: 256
    },
    {
    }
]
  1. Now you get every images size, so you can decide every cell size. Then you can download image when cell is going to be displayed.

It doesn't matter where you host the images, what is matter is that if your server knows image size.

rlam3 commented 8 years ago

Yes, this was pretty much along the lines I was saying. But only problem is these async alamofire tasks are handled asynchronously and the CGSize is called before we can obtain the results form the async commands...

Before "cellForItemAtIndexPath" can run to deqeue and configure the cell with images and data etc... "sizeForItemAtIndexPath" runs before that. Therefore it is not helpful.

Even If we were to run an async alamofire request to obtain a JSON. We still require it to return something prior to async command finishing the following func. And usually this will return nil.

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
jakecraige commented 8 years ago

@rlam3 One way you could handle that case is by returning 0 cells from numberOfItemsInSection for the relevant section and once the async request(s) come back, you can store the results somewhere and reload the collectionView. Then you would access the results synchronously in sizeForItemAtIndexPath.

Another way might be to return a default size, and once the request comes back for the cell, reload that cell, though that can be a bit jumpy since it would lay them out once, and then have to update it again.

leancmscn commented 8 years ago

hard code:

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)

        let imageView = cell.viewWithTag(100) as! UIImageView
        imageView.layer.cornerRadius = 4.0
        imageView.clipsToBounds = true

        let json = jsonOfCourses[indexPath.row]
        let json = jsonOfCourses[indexPath.row]
        if let urlStr = json["pictureUrl"].string, urlStrEncode = urlStr.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()), url = NSURL(string: urlStrEncode) {
            imageView.sd_setImageWithURL(url) { (img, err, nil, url) in
                self.collectionView.reloadItemsAtIndexPaths([indexPath])
            }
        } else {
            print("url nil, pls check api")
        }

        return cell
    }
    // MARK: WaterfallLayoutDelegate
    func collectionView(collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {

        var imgView: UIImageView!
        var itemSize = CGSizeMake(0, 0)
        if let subViews = collectionView.cellForItemAtIndexPath(indexPath)?.contentView.subviews {
            for view in subViews {
                if view.tag == 100 {
                    imgView = view as! UIImageView
                }
            }
        }
        if imgView != nil {
            if let img = imgView.image {
                let imgHeight = img.size.height
                let imgWidth = img.size.width
                let scale = imgHeight/imgWidth

                let itemWidth = (CGRectGetWidth(self.view.frame) - 30)/2
                let itemHeight = scale*itemWidth + 24

                itemSize = CGSizeMake(itemWidth, itemHeight)
            }
        }

        return itemSize

    }

bug: when you scroll the screen, items will re-layout, jump here and there

rlam3 commented 8 years ago

@leancmscn I've taken the approach of letting swift app know exactly what size the image is supposed to be. However, my problem now is how to annotate the image with necessary captions. Any ideas for this would be appreciated! Thanks!

chiahsien commented 7 years ago

@abhishekg219 You should provide actual image sizes, instead of default size.