-1

Using swift-5.0.1, iOS-12.2,

I observe a strange behaviour on a UICollectionView:

There are screen-wide cells that show an image inside my collectionView.

My observation is that whenever the image could not be fetched - in such a case, the above cell turns out to be bigger in size than expected. The question is why ??

The following image illustrates the problem:

enter image description here

Any image that cannot be fetched can obviously not be displayed and its cell gets the backgroundColer equal to red. This all works. But why is the one cell that is right above the nil-cell increased in size ???

All cells should be the size indicated by the yellow boxes.

I have two places inside my code where the height of a cell is touched:

  1. inside the UICollectionViewCell initialiser :

mainImageView.heightAnchor.constraint(equalToConstant: 150)

  1. inside the UICollectionViewController sizeForItemAt:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return .init(width: view.frame.width, height: 150)
    }

Both are set to 150 ! But in the error-case, the big cell is bigger than 150 - why ??

Here is the code in more detail:

import UIKit

class PhotoListCollectionViewCell: UICollectionViewCell {

    var mainImageView: UIImageView = {
        let imgV = UIImageView()
        imgV.contentMode = .scaleAspectFill
        return imgV
    }()
    var dateLabel = UILabel()
    var descriptionLabel = UILabel()
    var nameLabel = UILabel()
    var descContainerHeightConstraint = NSLayoutConstraint()
    var photoListCellViewModel : PhotoListCellViewModel? {
        didSet {
            nameLabel.text = photoListCellViewModel?.titleText
            descriptionLabel.text = photoListCellViewModel?.descText
            mainImageView.sd_setImage(with: URL( string: photoListCellViewModel?.imageUrl ?? "" ), completed: nil)
            dateLabel.text = photoListCellViewModel?.dateText
        }
    }

    func setConstraints() {

        addSubview(mainImageView)
        mainImageView.translatesAutoresizingMaskIntoConstraints = false
        mainImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        mainImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        mainImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        mainImageView.heightAnchor.constraint(equalToConstant: 150)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = .red
        self.setConstraints()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
class CollectionViewController: BaseListController, UICollectionViewDelegateFlowLayout {

    fileprivate let cellId = "cellId"

    lazy var viewModel: PhotoListViewModel = {
        return PhotoListViewModel()
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        initView()
        initVM()
    }

    func initView() {
        self.navigationItem.title = "NavBar"
        collectionView.register(PhotoListCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
        collectionView.backgroundColor = .white
    }

    func initVM() { ... }

    func showAlert( _ message: String ) { ... }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModel.numberOfCells
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as? PhotoListCollectionViewCell else {
            fatalError("Cell could not be dequeued")
        }
        let cellVM = viewModel.getCellViewModel( at: indexPath )
        cell.photoListCellViewModel = cellVM

        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return .init(width: view.frame.width, height: 150)
    }
}

The below code is most likely irrelevant for the question (but for completeness reasons...)

class BaseListController: UICollectionViewController {

    init() {
        super.init(collectionViewLayout: UICollectionViewFlowLayout())
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

I also tried to change imgV.contentMode = .scaleAspectFill to imgV.contentMode = .scaleAspectFit. But then the I loose the full width of the images as seen in the illustration here:

enter image description here

But I think I am getting closer. What could be the ideal setting ?

iKK
  • 6,394
  • 10
  • 58
  • 131
  • Hey, you really need to only add the relevant code. No one will read an entire application just to answer a question about a collection view...Simplify, add only the relevant code. – Mykod May 23 '19 at 19:31
  • If I would only know which code is not relevant here. But I am glad you tell me. Since I am completely un-sure on where this problem is coming from, I place most of the code. But just look at the first two code-blocks. The rest ist for completeness reasons and I will mark it as such in a second... – iKK May 23 '19 at 19:34
  • Could you try changing imgV.contentMode = .scaleAspectFill to imgV.contentMode = . scaleAspectFit just to make sure that's not it? edit: Also here detailVC.imageView.contentMode = .scaleAspectFill – Mykod May 23 '19 at 19:38
  • I just did that (and you can see the result in the extra illustration image I just added). Unfortunately, I loose the full width of the image. However, it did help in terms of the getting-bigger-issue.... Do you have any other suggestion ? (maybe it is somehow an issue with the anchoring... Any idea to this respect? How is the placement of an image view normally made inside a CollectionViewCell ?) – iKK May 23 '19 at 19:46
  • 1
    That's good. Then if you try scaleToFill it'll probably fix it. – Mykod May 23 '19 at 19:48
  • Indeed, `scaleToFill` did improve tremendously !! Thank you very much !!!! It does look correct now during scrolling down. However, there is still an issue: i.e. When scrolling up (after having scrolled down), the cells get messed up again in height. (but maybe this is another problem). Or do you have an idea why scrolling back up causes a new issue now ? – iKK May 23 '19 at 19:52
  • 1
    Maybe try inside the detailVC.imageView.sd_setImage block, adding self.view.layoutIfNeeded() ? Otherwise like I said, please reduce the amount of code so that we can help you. – Mykod May 23 '19 at 19:56
  • If I knew only which code to reduce... It seems all important since I have no clue where to dig for this issue. (your suggestion about detailVC-completion block did not help since this code indeed is irrelevant - I kick it out since it only applies when a cell is clicked...). But all the rest I am really not sure about it being irrelevant.... – iKK May 23 '19 at 20:01
  • @Mykod, I cleaned out a lot of the unnecessary code now from my original post (after knowing what the issue is...). Thank you again, for your great support !! (maybe you can cancel the -1 now, thanks). – iKK May 24 '19 at 07:51
  • Hello there. Glad you fixed it ;) Btw, it wasn't me who downvoted you question. – Mykod May 24 '19 at 07:53
  • lol - oh sorry of having suspected it was you ;) I am sorry. – iKK May 24 '19 at 07:55

1 Answers1

2

I finally found a solution.

With the fantastic help of Mykod, I was able to play around with the contentMode of the imageView being displayed inside the collectionViewCell as well as its anchor-constraints.

Here is the solution:

Inside PhotoListCollectionViewCell, you can set the contentMode as well as clipsToBounce:

var mainImageView: UIImageView = {
    let imgV = UIImageView()
    imgV.contentMode = .scaleAspectFill
    imgV.clipsToBounds = true
    return imgV
}()

And very importantly, you need to set the top-, leading-, trailing- and bottom-constraints equal to the cell's top-, leading-, trailing- and bottom constraints:


func setConstraints() {
    addSubview(mainImageView)
    mainImageView.translatesAutoresizingMaskIntoConstraints = false
    mainImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    mainImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
    mainImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
    mainImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}

With the above changes, the imageView now fills up the entire screen and is clipped a the correct height - i.e. all correct now :)

enter image description here

iKK
  • 6,394
  • 10
  • 58
  • 131