简体   繁体   中英

Put a mask layer on a UIView of varying size inside UITableViewCell

I have a UITableView whose cells contain a subview on which I need to perform three things:

  1. change its width constraint at runtime depending on a value inside an object specific to this cell
  2. change its background color depending on that same value
  3. round the top left and bottom left corners of the view but keep the corners on the right hand side as they are (so layer.cornerRadius is not an option)

I use the following code inside my custom UITableViewCell subclass to achieve the rounded corner effect on one side of the view only, which I call from tableView:cellForRowAt: :

func roundLeadingEdgesOfBar() {
    let roundedLayer = CAShapeLayer()
    roundedLayer.bounds = viewInQuestion.frame
    roundedLayer.position = viewInQuestion.center
    roundedLayer.path = UIBezierPath(roundedRect: viewInQuestion.bounds,
                                     byRoundingCorners: [.topLeft, .bottomLeft],
                                     cornerRadii: CGSize(width: 2, height: 2)).cgPath
    viewInQuestion.layer.mask = roundedLayer
    print("frame: \(viewInQuestion.frame)")
}

However what I see when I run this code is an effect like this: 在此处输入图片说明

The print statement in the code above produces the following output, indicating that viewInQuestion has the same frame every time when clearly on the screen it hasn't:

frame: (175.0, 139.5, 200.0, 5.0)
frame: (175.0, 139.5, 200.0, 5.0)
frame: (175.0, 139.5, 200.0, 5.0)
frame: (175.0, 139.5, 200.0, 5.0)

So I assume the width constraint on the view has not been rendered by the time I call this function. When I scroll the entire table view up until all cells are out of view, and then scroll them back into view, everything looks correct and the printed frames are all different, like I would expect:

frame: (136.5, 79.5, 238.5, 5.0)
frame: (169.5, 79.5, 205.5, 5.0)
frame: (226.0, 79.5, 149.0, 5.0)
frame: (247.5, 79.5, 127.5, 5.0)

I've read several times on SO to execute code that is dependent on constraints having been applied from within layoutSubviews , but that gave me the same result. I even tried calling roundLeadingEdgesOfBar from within tableView:willDisplay:forRowAt: , to no avail.

I also found this response to a similar problem which suggests putting the mask layer code inside drawRect: . This actually fixes 99% of the problem for me (leaving performance issues aside), but there are still corner cases (no pun intended) left for very long table views where I still see the wrong behavior.

My last resort was to call my rounding function via performSelector with a 0.00001 delay, which works in the sense that you see the bug for about a second on screen before it then disappears - still far from ideal behavior, let alone the awful code I had to write for it.

Is there any way to reliably apply the shape layer on the view inside a UITableViewCell using its correct runtime frame?

I think what you should be doing is that,

  1. Set the properties related to your current object in cellForRowAtIndexPath , try setting constraints in the setter of that particular value.
  2. Call setNeedsLayout which will make a future call to layoutIfNeeded -> layoutSubviews
  3. In your layoutSubviews , set the rounded corners.

I hope this will help you.

Instead of calling a function to "round the edges," I suggest creating a UIView subclass and let it handle the rounding.

For example:

class BulletBar: UIView {

    override func layoutSubviews() {

        let roundedLayer = CAShapeLayer()

        roundedLayer.frame = bounds

        roundedLayer.path = UIBezierPath(roundedRect: bounds,
                                         byRoundingCorners: [.topLeft, .bottomLeft],
                                         cornerRadii: CGSize(width: 2, height: 2)).cgPath
        layer.mask = roundedLayer

    }

}

Now, set the class of your "bar" subview in the cell to BulletBar. Use constraints to pin it to the right and bottom and to constrain the height and width. Create an IBOutlet for the width constraint, and then set the barWidthConstraint.constant as desired.

The class itself will handle rounding the corners.

Result:

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM