0

A rather frustrating dilema where a UICollectionViewCell is rendered with a UIImageView as it's only subview. The frame is loaded when an async call to load the image completes the call and calls the setImage() method. So usually the cell is in view already before an image may have been set.

However, when the image is then placed into the UIImageView when the cell is rendered, the image doesn't apply the contentMode property, so the image effectively floats and overlaps neighbouring cells in a rather peculiar fashion that jumbles around when scrolling.

When the cell is re-used, i.e, you scroll away from the broken area and then back into it, the cells appear normal again.

What you see when the cells load:

Scroll away then back into view, the cells organise themselves normally... The cells have an orange background color so they can be seen before the images have been loaded, all the cells do indeed render in the desired size/position. It's the image that overflows each cell (even though clipToBounds is true).

What causes this unusual behaviour? The image is loaded async from a server then cached locally. If I .reloadData() on the collection then the cells will also 'fix' themselves. Just after the first run or while loading do they appear all mixed up.

A code sample showing the cell re-use method and init method are as below:

class ThumbnailCell: UICollectionViewCell {
    var imageView: UIImageView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        imageView = UIImageView(frame: contentView.bounds)
        imageView.image = nil
        imageView.clipsToBounds = true
        imageView.contentMode = .ScaleAspectFill
        imageView.setTranslatesAutoresizingMaskIntoConstraints(false)
        contentView.addSubview(imageView)
        let viewsDictionary = ["imageView" : imageView]
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[imageView]", options: .allZeros, metrics: nil, views: viewsDictionary))
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[imageView]", options: .allZeros, metrics: nil, views: viewsDictionary))
    }

    func resetImage() {
        self.imageView.contentMode = .ScaleAspectFill
        self.imageView.frame = contentView.bounds
        self.imageView.clipsToBounds = true
    }

    func setImage(image: UIImage) {
        resetImage()
        self.imageView.image = image
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        self.imageView.image = nil
        resetImage()
    }
}
simbolo
  • 7,279
  • 6
  • 56
  • 96
  • It may have something to do with when you init the image view. Where/when are you doing this? Also you have objective-c as a tag and may want to remove that given it is swift. – Skyler Lauren Feb 14 '15 at 22:50
  • Thanks @SkylerLauren - I also think it might have something to do with the rending order. When the async call download the list is finished, a reloadData() is called and this also forces the collection to re-render normally. But it would seem strange to keep calling this? Anyway, I added a complete example. It's only a simple app so not sure why it's happening. – simbolo Feb 15 '15 at 11:04
  • 1
    I am not super useful when it comes to auto layout but I looks like the content view has constraints based on the image view which doesn't have a size or frame at that point. When you set the image you base the image view frame on the content view which is based on image view. This could explain why it is working correctly only after the first pass. Not 100% it will fix the problem but creating a frame for the image view in the init based on the width and height of the frame being passed in on the init might work. – Skyler Lauren Feb 15 '15 at 14:49
  • You should write that as an answer. The AutoLayout looked good to me, but if I just used the already set bounds, removing everything todo with AutoLayout (specifically `setTranslatesAutoresizingMaskIntoConstraints`) then the frames all appeared as normal. It works! – simbolo Feb 17 '15 at 10:17
  • 1
    So glad I was able to help. I was worried constantly calling reload would cause you issues down the road. I posted my comment as an answer and hopefully it will help others having a similar problem. It isn't the first question I have seen setTranslatesAutoresizingMaskIntoConstraints be the main issue. Good luck with your app =) – Skyler Lauren Feb 17 '15 at 14:38

2 Answers2

1

I am not super useful when it comes to auto layout but I looks like the content view has constraints based on the image view which doesn't have a size or frame at that point. When you set the image you base the image view frame on the content view which is based on image view. This could explain why it is working correctly only after the first pass. I am not 100% it will fix the problem but creating a frame for the image view in the init based on the width and height of the frame being passed in on the init might work.

setTranslatesAutoresizingMaskIntoConstraints

Could be the main offender.

Skyler Lauren
  • 3,792
  • 3
  • 18
  • 30
  • This is definitely not the solution for me. I coloured the image view's backdrop red so I could check its size and the size was correct, however the image did not stretch to fill the available space. – Ash Apr 19 '16 at 12:58
  • @Ash if the UIImageView is the correct size are you setting imageView.contentMode = .ScaleAspectFill? That is the only reason I can it wouldn't fill if the ImageView is the correct size. – Skyler Lauren Apr 19 '16 at 13:08
  • My problem turned out to be completely unrelated to the collection view; the images were being incorrectly parsed. – Ash Apr 21 '16 at 15:08
0

I was facing exactly same problem. The solution is from another question's answer.

Simply you just need to set estimated size of collection view to none using interface builder.

limon
  • 3,222
  • 5
  • 35
  • 52