uicollectionviewnslayoutconstraintdrawrect

Swift drawRect on UICollectionView subview with layout constraints draws in unexpected location


I am trying to draw onto a subview (subclass of UIView) that I have added to a UICollectionViewCell's contentView. I did this by implementing a drawRect function to draw a very simple cross in the center of the subview.

I've put in layout constraints to shift the subview slightly right into the contentView, and they appear to do what I want.

The problem is that when drawRect gets called, the cross doesn't appear in the center of the subview as intended. Rather, it gets shifted, by the same amount of shift the layout constraint tries to apply to the subview.

I used Apple's Implementing Modern Collection Views code as a base, and modified it as follows to implement the above.

In TextCell.swift (the custom cell implementation 'Cells and Supplementary Views'

  1. I added a custom UIView subclass which draws the cross.
class TextCellDrawingSubView: UIView {
    
   override init(frame: CGRect) { super.init(frame: frame) }
    
   required init?(coder: NSCoder) { fatalError("initWithCoder not implemnted") }
    
   override func draw(_ rect: CGRect) {          
     let path = UIBezierPath()
            
     let crosshairLength = 10.0 / 2.0
     let viewCenter = self.center  // ** this was wrong, see below **
     path.move(to:CGPoint(x:viewCenter.x - crosshairLength, y: viewCenter.y))
     path.addLine(to:CGPoint(x:viewCenter.x + crosshairLength, y: viewCenter.y))
            
     path.move(to:CGPoint(x:viewCenter.x,y: viewCenter.y - crosshairLength))
     path.addLine(to:CGPoint(x:viewCenter.x, y: viewCenter.y + crosshairLength))
            
     path.lineWidth = 1.0
     UIColor.green.setStroke()
     path.stroke()
}
  1. I added a property for the subview to the TextCell class.
class TextCell: UICollectionViewCell {
  let label = UILabel()
  let drawingSubView = TextCellDrawingSubView() //**this line added**
   ...
  1. I added the subview to the contentView, as well as the constraints, to the TextCell extension's "configure" function:
extension TextCell {
  func configure() {
   ...   

    drawingSubView.translatesAutoresizingMaskIntoConstraints = false
    drawingSubView.backgroundColor = .blue
    drawingSubView.alpha = 0.4
            
    // Position the drawingSubView at a specific offset left into the contentView.

   NSLayoutConstraint.activate([
     drawingSubView.widthAnchor.constraint(equalToConstant:50),
     drawingSubView.heightAnchor.constraint(equalToConstant:50),
     drawingSubView.topAnchor.constraint(equalTo:contentView.topAnchor),
     drawingSubView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20.0)
     ])

    //The cross doesn't appear in the center of the drawingSubView. 
    //It's as if the leadingAnchor constraint is applied twice, to the view, and then again to the cross.
  }
}
  1. I then run the project and select "Compositional Layout - Advanced Layouts - Orthogonal Sections - Orthogonal Sections'

The image below is what appears. I was expecting the (green) cross to be in the center of the dark blue subview, but it shifts right by the same offset that is specified by the leadingAnchor constraint, i.e. 20 points.

Edit: If I change the leadingAnchor constraint to just be equal to the contentView's leadingAnchor, i.e. change the constant from 20 to 0, the subview appears left-aligned in the contentView(s) - and the cross is centered in it.

(FWIW my ultimate objective is to draw 2 line diagrams into a cell, one with air temperatures in the last week, and one just below with the daily average atmospheric pressure in that time.)

I looked high and low in the Apple Developer documentation, watched WWDC Advances in UICollectionViews for 2019, 2020 and even 2016. No luck finding an explanation or a fix.

Edit: I am now reading through "Modern Autolayout" by K.Harrison again.

Edit: per the answer provided below by @DonMag:

In TextCell.swift, I replaced

 let viewCenter = self.center

with

let viewCenter = CGPoint(x:self.bounds.midX, y:self.bounds.midY)

Now it works as intended. enter image description here


Solution

  • In your override func draw(_ rect: CGRect), you are referencing:

    let viewCenter = self.center
    

    If the frame of self happens to be:

    origin = (x = 20.0, y = 0.0)
    size = (width = 50.0, height = 50.0)
    

    then viewCenter.x will equal 20.0 + 25.0.

    Change viewCenter to:

    let viewCenter = self.bounds.center
    

    and you should get your desired alignment.