0

I have a somewhat basic subclassed UICollectionView Cell. I want to add a UIImageView to it, so every cell displays an image.

The image is displaying properly if I add explicit values to x:y:width:height, but I can't use self.contentView.frame.width to determine the size of the Collection View cell (for placing the image on the x axis)

class SubclassedCell: UICollectionViewCell {
    var myImageView: UIImageView!
    required init?(coder aDecoder: NSCoder) {
        super.init(coder:aDecoder)
        myImageView = UIImageView(frame: CGRect(x: (self.frame.width - 10.0), y: 50.0, width: 20.0, height: 20.0))
        myImageView.image = UIImage(named: "JustinBieber")
        self.contentView.addSubview(myImageView)
    }
}

In the above, (self.contentView.frame.width - 10.0) is not getting the size of the collection view cell, so the image does not display at all. If I explicitly put in a value for x, say 0, it shows.

How can I determine the size (width) of the subclassed collection view cell?

Joe
  • 3,772
  • 3
  • 33
  • 64
  • Could you provide additional detail on what you are trying to accomplish? Eg. are you trying to position the image 10 points in from the right side of the cell? – kevin Apr 11 '17 at 01:36
  • Correct. I'm trying to position the image 10 points from the right side of the cell. That's why I need to determine the width of the cell dynamically (they're not all the same size). – Joe Apr 11 '17 at 03:25

2 Answers2

1

The initializer is called to early in the view lifecycle to accurately provide values for dimensions.

A more idiomatic approach would be to layout your image in the layoutSubviews life cycle method. A trivial example is illustrated below

class SubclassedCell: UICollectionViewCell {

    var myImageView: UIImageView!
    var imageDisplayed = false

    override func layoutSubviews() {
        super.layoutSubviews()
        if !imageDisplayed {
            myImageView = UIImageView(frame: CGRect(x: (self.frame.width - 10.0), y: 50.0, width: 20.0, height: 20.0))
            myImageView.image = UIImage(named: "JustinBieber")
            self.contentView.addSubview(myImageView)
            imageDisplayed = true
        }
    }
}

If you are using auto layout in your application, you may also want to consider adding the image and providing constraints instead of explicitly setting the image's frame.

As illustrated in this answer - depending on your use case - their may be better ways to set up your cell.

Community
  • 1
  • 1
syllabix
  • 2,240
  • 17
  • 16
  • 1
    Just to note, `layoutSubviews` can be called more than once during the lifecycle of the view – sooper Apr 11 '17 at 01:34
  • Thanks for the help, but like @sooper mentioned, layoutSubviews gets called several times, so this causes the cells to basically spew imageViews everywhere. – Joe Apr 11 '17 at 03:26
  • @joe - I have modified this example to add the image to the view only once on the initial call to `layoutSubviews` - there are many ways to accomplish what you are looking to do - the main thing to consider is what properties of views are useful at what point in their lifecycle – syllabix Apr 11 '17 at 03:35
  • p.s - typo in the property name: var imageDispalyed = false (i tried editing it, but edits need to be 6 characters) – Joe Apr 11 '17 at 03:50
  • Have you tried placing the logic in `awakeFromNib`? – sooper Apr 11 '17 at 17:22
1

There are many ways to achieve this layout, with constraints you don't need to know the width. You define the relationship you want, and the layout system manages the rest.

class SubclassedCell1: UICollectionViewCell {
    var myImageView: UIImageView!

    private func commonInit() {
        myImageView = UIImageView()
        myImageView.image = UIImage(named: "JustinBieber")
        myImageView.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(myImageView)

        NSLayoutConstraint.activate([
            myImageView.widthAnchor.constraint(equalToConstant: 20),
            myImageView.heightAnchor.constraint(equalToConstant: 20),
            myImageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 50),
            myImageView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 10),
            ])
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder:aDecoder)
        commonInit()
    }
}

You can also use layoutSubviews, as @syllabix mentioned. When doing this, make sure to update the frame every call to layoutSubviews or your view will not update properly if it is resized. Also use the parent views bounds, not frame, when laying out its children.

class SubclassedCell2: UICollectionViewCell {
    var myImageView: UIImageView!

    private func commonInit() {
        myImageView = UIImageView()
        myImageView.image = UIImage(named: "JustinBieber")
        self.contentView.addSubview(myImageView)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder:aDecoder)
        commonInit()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        myImageView.frame = CGRect(x: (self.bounds.maxX - 10.0), y: 50.0, width: 20.0, height: 20.0)
    }
}
kevin
  • 2,021
  • 21
  • 20