CALayer shadow in UITableViewCell Drawn incorrectly
Asked Answered
M

4

8

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: enter image description here

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

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..

Multipurpose answered 27/7, 2016 at 21:53 Comment(2)
It seems you are calling this before the cell's bounds have been set to the correct size. If you're using auto layout you might try overriding layoutSubviews in your UITableViewCell subclass and calling it there after calling super.layoutSubviews()Glenn
@Glenn actually it's already a sublclass of UITableViewCell and this method is called from layoutSubViews. I've traced it down to the applying the shadow path, see edit.Multipurpose
G
7

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];
    }
}
Glenn answered 2/8, 2016 at 23:1 Comment(2)
Works great (apart that _frame = frame needs to be [super setFrame:frame]), thank you. What I still don't understand is why this works and applying the shadow in layoutSubviews doesn't, because I see this method being called on device rotation with a different frame.Multipurpose
Many people think that layoutSubviews is directly analogous to viewDidLayoutSubviews (i.e. that is called after every pass of the layout engine) this is not the case. For a short adumbration of when it is called see here.Glenn
H
1

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.

Hildick answered 4/8, 2016 at 1:4 Comment(1)
The shadow needs to be added to a subview and not the whole cell (contentView). I already update the path in layoutSubviews though.Multipurpose
F
0

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.

Flag answered 18/1, 2018 at 7:46 Comment(0)
P
0

Thanks for @beyowulf's answer gave me clues in override UIView frame get and set

In my case, I would like to make shadow stick with the other subview in subclass tableView cell.

Swift 5

// TargetView old size
var lastSize: CGSize = .zero

// Override frame in subclass tableView cell
override var frame: CGRect {
    get {
       super.frame
    }
    set {
       super.frame = newValue
       if targetView != nil {
           // Compared targetView size with old one.
          if lastSize != targetView.frame.size {
              
             /* Update the other subview's shadow path or layer frame here */
              
             lastSize = targetView.frame.size
         }
      }
   }
}

It works for me.

Pierian answered 30/12, 2020 at 3:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.