UITableView layout messing up on push segue and return. (iOS 8, Xcode beta 5, Swift)

后端 未结 7 880
清歌不尽
清歌不尽 2020-12-07 13:44

tldr; Auto constrains appear to break on push segue and return to view for custom cells

Edit: I have provided a github example proj

相关标签:
7条回答
  • 2020-12-07 14:15

    If you have set tableView's estimatedRowHeight property.

    tableView.estimatedRowHeight = 100;
    

    Then comment it.

    //  tableView.estimatedRowHeight = 100;
    

    It solved the bug which occurs in iOS8.1 for me.

    If you really want to keep it,then you could force tableView to reloadData before pushing.

    [self.tableView reloadData];
    [self.navigationController pushViewController:vc animated:YES];
    

    or do it in viewWillDisappear:.

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

    Hope it helps.

    0 讨论(0)
  • 2020-12-07 14:17

    This bug is caused by having no tableView:estimatedHeightForRowAtIndexPath: method. It's an optional part of the UITableViewDelegate protocol.

    This isn't how it's supposed to work. Apple's documentation says:

    Providing an estimate the height of rows can improve the user experience when loading the table view. If the table contains variable height rows, it might be expensive to calculate all their heights and so lead to a longer load time. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.

    So this method is supposed to be optional. You'd think if you skipped it, it would fall back on the accurate tableView:heightForRowAtIndexPath:, right? But if you skip it on iOS 8, you'll get this behaviour.

    What seems to be happening? I have no internal knowledge, but it looks like if you do not implement this method, the UITableView will treat that as an estimated row height of 0. It will compensate for this somewhat (and, at least in some cases, complain in the log), but you'll still see an incorrect size. This is quite obviously a bug in UITableView. You see this bug in some of Apple's apps, including something as basic as Settings.

    So how do you fix it? Provide the method! Implement tableView: estimatedHeightForRowAtIndexPath:. If you don't have a better (and fast) estimate, just return UITableViewAutomaticDimension. That will fix this bug completely.

    Like this:

    - (CGFloat)tableView:(UITableView *)tableView
    estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
        return UITableViewAutomaticDimension;
    }
    

    There are potential side effects. You're providing a very rough estimate. If you see consequences from this (possibly cells shifting size as you scroll), you can try to return a more accurate estimate. (Remember, though: estimate.)

    That said, this method is not supposed to return a perfect size, just a good enough size. Speed is more important than accuracy. And while I spotted a few scrolling glitches in the Simulator there were none in any of my apps on the actual device, either the iPhone or iPad. (I actually tried writing a more accurate estimate. But it's hard to balance speed and accuracy, and there was simply no observable difference in any of my apps. They all worked exactly as well as just returning UITableViewAutomaticDimension, which was simpler and was enough to fix the bug.)

    So I suggest you do not try to do more unless more is required. Doing more if it is not required is more likely to cause bugs than fix them. You could end up returning 0 in some cases, and depending on when you return it that could lead to the original problem reappearing.

    The reason Kai's answer above appears to work is that it implements tableView:estimatedHeightForRowAtIndexPath: and thus avoids the assumption of 0. And it does not return 0 when the view is disappearing. That said, Kai's answer is overly complicated, slow, and no more accurate than just returning UITableViewAutomaticDimension. (But, again, thanks Kai. I'd never have figured this out if I hadn't seen your answer and been inspired to pull it apart and figure out why it works.)]

    Note that you may also need to force layout of the cell. You'd think iOS would do this automatically when you return the cell, but it doesn't always. (I will edit this once I investigate a bit more to figure out when you need to do this.)

    If you need to do this, use this code before return cell;:

    [cell.contentView setNeedsLayout];
    [cell.contentView layoutIfNeeded];
    
    0 讨论(0)
  • 2020-12-07 14:18

    In xcode 6 final for me the workaround does not work. I am using custom cells and dequeing a cell in heightForCell leads to infinity loop. As dequeing a cell calls heightForCell.

    And still the bug seems to be present.

    0 讨论(0)
  • 2020-12-07 14:23

    Well, until it works, you can delete those two line:

    self.tableView.estimatedRowHeight = 45
    self.tableView.rowHeight = UITableViewAutomaticDimension
    

    And add this method to your viewController:

    override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell") as TableViewCell
        cell.cellLabel.text = self.tableArray[indexPath.row]
    
        //Leading space to container margin constraint: 0, Trailling space to container margin constraint: 0
        let width = tableView.frame.size.width - 0
        let size = cell.cellLabel.sizeThatFits(CGSizeMake(width, CGFloat(FLT_MAX)))
    
        //Top space to container margin constraint: 0, Bottom space to container margin constraint: 0, cell line: 1
        let height = size.height + 1
    
        return (height <= 45) ? 45 : height
    }
    

    It worked without any other changes in your test project.

    0 讨论(0)
  • 2020-12-07 14:35

    The problem of this behavior is when you push a segue the tableView will call the estimatedHeightForRowAtIndexPath for the visible cells and reset the cell height to a default value. This happens after the viewWillDisappear call. If you come back to TableView all the visible cells are messed up..

    I solved this problem with a estimatedCellHeightCache. I simply add this code snipped to the cellForRowAtIndexPath method:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        ...
        // put estimated cell height in cache if needed
        if (![self isEstimatedRowHeightInCache:indexPath]) {
            CGSize cellSize = [cell systemLayoutSizeFittingSize:CGSizeMake(self.view.frame.size.width, 0) withHorizontalFittingPriority:1000.0 verticalFittingPriority:50.0];
            [self putEstimatedCellHeightToCache:indexPath height:cellSize.height];
        }
        ...
    }
    

    Now you have to implement the estimatedHeightForRowAtIndexPath as following:

    -(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
        return [self getEstimatedCellHeightFromCache:indexPath defaultHeight:41.5];
    }
    

    Configure the Cache

    Add this property to your .h file:

    @property NSMutableDictionary *estimatedRowHeightCache;
    

    Implement methods to put/get/reset.. the cache:

    #pragma mark - estimated height cache methods
    
    // put height to cache
    - (void) putEstimatedCellHeightToCache:(NSIndexPath *) indexPath height:(CGFloat) height {
        [self initEstimatedRowHeightCacheIfNeeded];
        [self.estimatedRowHeightCache setValue:[[NSNumber alloc] initWithFloat:height] forKey:[NSString stringWithFormat:@"%d", indexPath.row]];
    }
    
    // get height from cache
    - (CGFloat) getEstimatedCellHeightFromCache:(NSIndexPath *) indexPath defaultHeight:(CGFloat) defaultHeight {
        [self initEstimatedRowHeightCacheIfNeeded];
        NSNumber *estimatedHeight = [self.estimatedRowHeightCache valueForKey:[NSString stringWithFormat:@"%d", indexPath.row]];
        if (estimatedHeight != nil) {
            //NSLog(@"cached: %f", [estimatedHeight floatValue]);
            return [estimatedHeight floatValue];
        }
        //NSLog(@"not cached: %f", defaultHeight);
        return defaultHeight;
    }
    
    // check if height is on cache
    - (BOOL) isEstimatedRowHeightInCache:(NSIndexPath *) indexPath {
        if ([self getEstimatedCellHeightFromCache:indexPath defaultHeight:0] > 0) {
            return YES;
        }
        return NO;
    }
    
    // init cache
    -(void) initEstimatedRowHeightCacheIfNeeded {
        if (self.estimatedRowHeightCache == nil) {
            self.estimatedRowHeightCache = [[NSMutableDictionary alloc] init];
        }
    }
    
    // custom [self.tableView reloadData]
    -(void) tableViewReloadData {
        // clear cache on reload
        self.estimatedRowHeightCache = [[NSMutableDictionary alloc] init];
        [self.tableView reloadData];
    }
    
    0 讨论(0)
  • 2020-12-07 14:35

    If none of the above worked for you (as it happened to me) just check the estimatedRowHeight property from the table view is kind of accurate. I checked I was using 50 pixels when it was actually closer to 150 pixels. Updating this value fixed the issue!

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = tableViewEstimatedRowHeight // This should be accurate.
    
    0 讨论(0)
提交回复
热议问题