CALayer shadow in UITableViewCell Drawn incorrectl

2020-07-09 03:02发布

问题:

I am applying shadow to a UITableViewCell using CALayer.

Here's my code:

- (void)addShadowToView:(UIView *)view
{
    // shadow
    view.layer.shadowColor = [[UIColor colorWithWhite:0.0f alpha:0.1f] CGColor];
    view.layer.shadowOpacity = 1.0f;
    view.layer.shadowOffset = CGSizeMake(0.0f, 3.0f);
    view.layer.shadowRadius = 6.0f;

    CGRect shadowFrame = view.layer.bounds;
    CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:shadowFrame].CGPath;
    view.layer.shadowPath = shadowPath;
}

The issue is that for some tableviewcells, the shadow does not span the entire width of the cell. For some cells it would be correct, for others it would be faulty. I do notice that the rotation of the device also affects it, and reloading of the tableview data sometimes solves it.

What is the best way to mitigate this issue (and with that I don't mean to reload the whole tableview on each rotation etc.)?

Example bottom of cell where shadow is correctly applied:

Bottom of cell in same tableview after scrolling down (shadow only applied for first 75% of width):

Edit: I have noticed the issue is caused from these lines of code:

CGRect shadowFrame = view.layer.bounds;
CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:shadowFrame].CGPath;
view.layer.shadowPath = shadowPath;

If I leave them out, everything is fine. But I've been told there is certain performance benefit when using this. Somehow the shadow is not correctly applied to new dimensions after rotating..

回答1:

You can override the setter for you're cell's frame and call addShadowToView:. You can optimize this more by storing your cell's size and only updating the shadow path when the size changes for example:

@property (nonatomic, assign) CGSize size;

And

- (void) setFrame:(CGRect)frame
{
    [super setFrame:frame];
    // Need to check make sure this subview has been initialized
    if(self.subviewThatNeedsShadow != nil && !CGSizeEqualToSize(self.size,_frame.size)
    {
        [self addShadowToView: self.subviewThatNeedsShadow];
    }
}


回答2:

The easiest solution is to add the shadow to the UITableViewCell's contentView (vs the layer for the cell's backing view). Since the cell's bounds change on scroll, if you add the shadow to the root view then you would have to update the shadow's path on each scroll event which would be costly and not necessary.

You're definitely correct re: the performance hit by not explicitly setting the shadowPath though. If you don't have any animated content within the cell, I'd also recommend rasterizing it to further improve performance.

EDIT: You must also ensure that when you set the shadow path that the contentView's bounds are in their 'final' position. If the size of the cell is later modified, this will result in the contentView's bounds changing and thus an incorrect shadowPath. The solution to this is to update the path in the UITableViewCell's -layoutSubviews method.



回答3:

Here the concern is not the parent view frame where your working here concern is its sublayer and its size which should be changed when layout changes. You can override the below method which will help you to setup correct frame on layout changing.

public override void LayoutSublayersOfLayer(CALayer layer)
{

     base.LayoutSublayersOfLayer(layer);
     if (layer.Name == "gradient")
     {
          layer.Frame = view.Layer.Frame;
     }
} 

In above code view is the where you added sublayer. If you are playing with multiple layers in same view than you can use the identifier name property to work on particular layer.