I'm modifying a table view cell during a call to tableView:cellForIndexPath
, but the modifications do not appear until the cell is scrolled off and then back on. I realize that I can modify the .xib file or can alloc/init a cell rather than calling dequeue
, but I'd like to understand what is going on.
The changes involve masking the cell to achieve rounded bottom corners. If I set the corner radius instead (i.e. cell.layer.cornerRadius = ...
), which rounds all the corners, I don't see the problem.
Update:
I didn't post the code initially because it was proprietary, involved a lot of complexity and I thought the question was a reasonable one in absence of any specifics. In light of the responses, though, here is a reduced fragment which exhibits the problem, with the only change being the actual cell identifier. I also updated the description/question to simplify the scenario.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TheCorrectCellIdentifier" forIndexPath:indexPath];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:cell.bounds byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(4., 4.)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = cell.bounds;
maskLayer.path = maskPath.CGPath;
cell.layer.masksToBounds = YES;
cell.layer.mask = maskLayer;
return cell;
}
Reload the tableView in
viewDidAppear
orviewDidLayoutSubviews
method will make it to work.Reason why your code not worked: Actually, as per view life cycle, the methods are called in following manner,
init
,loadView
,viewDidLoad
,viewWillAppear
,viewWillLayoutSubviews
(called more than once),viewDidLayoutSubviews
(called multiple time) and lastlyviewDidAppear
.Now, actually the tableview get loaded in method
viewDidLoad
, whereas the autolayout constraints that you apply were going to be applied after that method. Thats why, it is not showing expected result. Reloading it again after autolayout constraints get applied make it work. Thats'why above code will surely work.One more point to note: In storyboard if you have designed your viewController by using iphone 5 size and then if you run the code in iphone 5, then without reloading it, it must work, but if you run it in any other sized viewController, then it will not work. The reason behind it is,
viewDidLoad
method get called, then any view have size that was in its storyboard.If you have any doubts, let me know
I can't reproduce your issue on iOS 9.2, as your code is working fine for me.
If you are supporting older versions, I believe the issue you are facing is related to the cell's initial width not corresponding to the tableView's width. Before a cell is laid out, its initial width was based on its Storyboard width (which differs from the actual screen width).
What happens is that your shapeLayer mask starts out out with the wrong bounds, so the mask won't appear correctly until the cell is reused. At that point, since the cell has been laid out, its width is now correct, the new mask's bounds are correct, and the proper effect is achieved.
While you can try to perform layout in
cellForRowAtIndexPath
, it involves a bit more code to handle edge cases like initial cell width issues.It's a lot easier to apply the mask after the cell has been laid out, such as technerd suggested in their answer.
That's the explanation for the problem.
Since that doesn't involve adding a shape layer with a specific frame size, you won't run into an issue. The cell's layer will be the proper size when rendering occurs, so the rounded corners will show up correctly.
How this had needed to be handled
I don't know what version of iOS you need to support (since this issue has been fixed in recent versions), but here is one of the ways to work around the issue where the cell was not initially laid out properly until you scrolled off-screen, then back on-screen.
You may be able to get away with
setNeedsLayout
;layoutIfNeeded
. In my app, I had needed the constraints to be updated for other reasons.Your code working fine . But if you still facing Corner radius issue in
cellForRowAtIndexPath:
then try inwillDisplayCell:
. I tried in both methods and working fine.willDisplayCell:
According to me, Apple provide two different set of methods (Protocol) named
UITableViewDataSource
andUITableViewDelegate
to manage UITableView.UITableViewDataSource : Data source provides actual data for cell to fill in the details.
UITableViewDelegate : At the other end delegate is responsible for give information about visual layout of tableview and handles user interaction.
So, conclusion is that DataSource mainly deal with content(Data) of table and Delegate deal with presentation and interaction of tableview component.
Lets come to way we dealing with TableView.
We all are very personal with UITableViewDataSource method
tableView:cellForRowAtIndexPath:
, This method is responsible for to create or reuse valid UITableViewCell with proper detailed information for each index path.What we tend to do cell layout configuration and UI modification in this method.After
tableView:cellForRowAtIndexPath:
is called, the system does a layout configuration, then callstableView:willDisplayCell:forRowAtIndexPath:
method before cells display on screen.So we can use this method for view configuration of Tableview cell(May be, Apple propose this method for us!!!).
So I persionally prefer to use
tableView:cellForRowAtIndexPath:
to create cell and left cell UI configuration task fortableView:willDisplayCell:forRowAtIndexPath:
method.Apply mask in
willDisplayCell:
Here is my
cellForRowAtIndexPath:
Output :
This method won't be called until the cell needs to be recycled (i.e appears from off-screen). Cells already in the tableview don't have this method called since they are already visible. I'm wondering (since this code works fine) if you have confused the delegate callback? These methods only trigger when a new cell scrolls into view (and a cached cell needs to be reconfigured).
if you call
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
you would be able to trigger a redraw on a cell, or[tableview reloadData]
to refresh all cells