Table view cell only masks correctly after scrolli

2019-03-15 08:11发布

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;
}

6条回答
SAY GOODBYE
2楼-- · 2019-03-15 08:27

Reload the tableView in viewDidAppear or viewDidLayoutSubviews method will make it to work.

-(void)viewDidAppear:(BOOL)animated
{
 [super viewDidAppear:animated];
 [self.tableView reloadData];
}

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

查看更多
虎瘦雄心在
3楼-- · 2019-03-15 08:30
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cell.reloadInputViews()
}
查看更多
狗以群分
4楼-- · 2019-03-15 08:35

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.

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.

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.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    // Workaround for visible cells not laid out properly since their layout was
    // based on a different (initial) width from the tableView.

    CGRect rect = cell.frame;
    rect.size.width = CGRectGetWidth(tableView.bounds);
    cell.frame = rect;

    ... configure cell

    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];

    return cell;
}

You may be able to get away with setNeedsLayout; layoutIfNeeded. In my app, I had needed the constraints to be updated for other reasons.

查看更多
Melony?
5楼-- · 2019-03-15 08:37

Your code working fine . But if you still facing Corner radius issue in cellForRowAtIndexPath: then try in willDisplayCell: . I tried in both methods and working fine.

willDisplayCell:

Tells the delegate the table view is about to draw a cell for a particular row.

A table view sends this message to its delegate just before it uses cell to draw a row, thereby permitting the delegate to customize the cell object before it is displayed. This method gives the delegate a chance to override state-based properties set earlier by the table view, such as selection and background color. After the delegate returns, the table view sets only the alpha and frame properties, and then only when animating rows as they slide in or out. Here is my code.

According to me, Apple provide two different set of methods (Protocol) named UITableViewDataSource and UITableViewDelegate 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 calls tableView: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 for tableView:willDisplayCell:forRowAtIndexPath: method.

Apply mask in willDisplayCell:

 -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{

    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:cell.bounds byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight                                                         cornerRadii:CGSizeMake(10.0, 10.0)];
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = cell.bounds;
    maskLayer.path = maskPath.CGPath;
    cell.layer.masksToBounds = YES;
    cell.layer.mask = maskLayer;
    }

Here is my cellForRowAtIndexPath:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

static NSString *identifier = @"cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];

cell.textLabel.text = [NSString stringWithFormat:@"Row : %ld ", (long)indexPath.section];
return cell;
}

Output :

enter image description here

查看更多
闹够了就滚
6楼-- · 2019-03-15 08:38

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.

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

查看更多
何必那么认真
7楼-- · 2019-03-15 08:44
Approach - 1 reload cell in viewWillAppear with delay mathod

func viewWillAppear(_ animated: Bool)
{ 
   super.viewWillAppear(animated) 
   DispatchQueue.main.asyncAfter(deadline: .now() + 0.10, execute: {
        self.tableView.reloadData() }) 
}

Approach - 2 Draw in GCD

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TheCorrectCellIdentifier" forIndexPath:indexPath];

dispatch_async(dispatch_get_main_queue(), ^{

    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;
}
查看更多
登录 后发表回答