Подтвердить что ты не робот

Макет UITableView помещается на push segue и возвращается. (iOS 8, Xcode beta 5, Swift)

TL;DR; Автоматические ограничения, по-видимому, разбиваются на push segue и возвращаются к просмотру для пользовательских ячеек

Изменить. Я представил проект примера github, который показывает ошибку, которая возникает https://github.com/Matthew-Kempson/TableViewExample.git

Я создаю приложение, которое требует, чтобы метка заголовка пользовательского UITableCell позволяла изменять строки, зависящие от длины заголовка сообщения. Ячейки загружаются в представление правильно, но если я нажимаю на ячейку, чтобы загрузить сообщение в push-сеге на представление, содержащее WKWebView, вы можете видеть, как показано на снимке экрана, ячейки немедленно перемещаются в неправильные позиции. Это также просматривается при загрузке представления обратно через кнопку возврата в UINavigationController.

В этом конкретном примере я нажал на самую концевую ячейку, с названием "Два приятеля, которые я снял в Париже", и все загрузилось правильно. Затем, как показано на следующем снимке экрана, все камеры движутся вверх по неизвестным причинам на фоне загрузки второго представления. Затем, когда я загружаю представление назад, вы можете видеть, что экран слегка сдвинулся вверх, и я не могу прокручивать его ниже, чем показано. Это кажется случайным, как и при других тестах, когда изображение загружается обратно, в нижней ячейке отсутствует пустое пространство, которое не исчезает.

Я также включил изображение, содержащее ограничения, которые имеют ячейки.

Изображения (мне нужно больше репутации, чтобы обеспечить изображения в этом вопросе, видимо, чтобы они были в этом альбоме imgur): http://imgur.com/a/gY87E

Мой код:

Метод в пользовательской ячейке, чтобы позволить ячейке правильно изменять размер представления при вращении:

override func layoutSubviews() {
    super.layoutSubviews()

    self.contentView.layoutIfNeeded()

    // Update the label constaints
    self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.width
    self.detailsLabel.preferredMaxLayoutWidth = self.detailsLabel.frame.width
}

Код в виде таблицы

override func viewDidLoad() {
    super.viewDidLoad()

    // Create and register the custom cell
    self.tableView.estimatedRowHeight = 56
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

Код для создания ячейки

    override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    if let cell = tableView.dequeueReusableCellWithIdentifier("LinkCell", forIndexPath: indexPath) as? LinkTableViewCell {

        // Retrieve the post and set details
        let link: Link = self.linksArray.objectAtIndex(indexPath.row) as Link

        cell.titleLabel.text = link.title
        cell.scoreLabel.text = "\(link.score)"
        cell.detailsLabel.text = link.stringCreatedTimeIntervalSinceNow() + " ago by " + link.author + " to /r/" + link.subreddit

        return cell
    }

    return nil
}

Если вам требуется больше кода или информации, пожалуйста, спросите, и я предоставлю необходимое

Спасибо за вашу помощь!

4b9b3361

Ответ 1

Проблема такого поведения заключается в том, что вы нажимаете segue, tableView вызывает estimatedHeightForRowAtIndexPath для видимых ячеек, а reset - высоту ячейки до значения по умолчанию. Это происходит после вызова viewWillDisappear. Если вы вернетесь в TableView, все видимые ячейки будут испорчены.

Я решил эту проблему с помощью estimatedCellHeightCache. Я просто добавляю этот код в метод cellForRowAtIndexPath:

- (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];
    }
    ...
}

Теперь вы должны реализовать estimatedHeightForRowAtIndexPath следующим образом:

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

Настроить кеш

Добавьте это свойство в файл .h:

@property NSMutableDictionary *estimatedRowHeightCache;

Внедрить методы для put/get/ reset.. 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];
}

Ответ 2

Эта ошибка вызвана отсутствием метода tableView:estimatedHeightForRowAtIndexPath:. Это необязательная часть протокола UITableViewDelegate.

Это не то, как он должен работать. Документация Apple гласит:

Обеспечение оценки высоты строк может улучшить пользовательский интерфейс при загрузке представления таблицы. Если таблица содержит строки с переменной высотой, может быть дорого рассчитать все их высоты и, следовательно, привести к более длительному времени загрузки. Использование оценки позволяет отложить часть стоимости вычисления геометрии от времени загрузки до времени прокрутки.

Таким образом, этот метод предполагается необязательным. Вы могли бы подумать, что если вы его пропустили, он вернется к точному tableView:heightForRowAtIndexPath:, правильно? Но если вы пропустите его на iOS 8, вы получите это поведение.

Что, похоже, происходит? У меня нет внутренних знаний, но похоже, что если вы не реализуете этот метод, UITableView будет рассматривать это как примерную высоту строки 0. Это компенсирует это несколько (и, по крайней мере, в некоторых случаях, жалуется в журнале), но вы по-прежнему увидите неправильный размер. Очевидно, это ошибка в UITableView. Вы видите эту ошибку в некоторых приложениях Apple, включая что-то как основное, как "Настройки".

Итак, как вы это исправите? Предоставьте метод! Внедрите tableView: estimatedHeightForRowAtIndexPath:. Если у вас нет лучшей (и быстрой) оценки, просто верните UITableViewAutomaticDimension. Это полностью исправит эту ошибку.

Вот так:

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

Существуют потенциальные побочные эффекты. Вы даете очень приблизительную оценку. Если вы видите последствия этого (возможно, изменения размера ячейки при прокрутке), вы можете попытаться вернуть более точную оценку. (Помните, что: оценка.)

Тем не менее, этот метод не должен возвращать идеальный размер, достаточно хороший размер. Скорость важнее точности. И хотя я заметил несколько прокручиваемых глюков в симуляторе, в любом из моих приложений на самом устройстве, iPhone или iPad, было none. (На самом деле я пытался написать более точную оценку, но было трудно сбалансировать скорость и точность, и в любом из моих приложений просто не наблюдалось различий. Все они работали точно так же, как просто возвращали UITableViewAutomaticDimension, что было проще и было достаточно, чтобы исправить ошибку.)

Поэтому я предлагаю вам не пытаться делать больше, если больше не требуется. Выполнение большего, если это не требуется, скорее всего вызовет ошибки, чем исправить их. В некоторых случаях вы можете вернуть 0 и в зависимости от того, когда вы его вернете, что может привести к появлению исходной проблемы.

Причина, по которой Кай отвечает выше, работает, заключается в том, что она реализует tableView:estimatedHeightForRowAtIndexPath: и, таким образом, избегает предположения о 0. И он не возвращает 0, когда вид исчезает. Тем не менее, ответ Кай слишком сложный, медленный и не более точный, чем просто возвращение UITableViewAutomaticDimension. (Но, опять же, спасибо Каю. Я бы никогда не подумал об этом, если бы не видел вашего ответа и был вдохновлен разделить его и выяснить, почему он работает.)]

Обратите внимание, что вам также может потребоваться принудительная компоновка ячейки. Вы думаете, что iOS будет делать это автоматически, когда вы вернете ячейку, но это не всегда. (Я отредактирую это, как только я исследую немного больше, чтобы выяснить, когда вам нужно это делать.)

Если вам нужно это сделать, используйте этот код до return cell;:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

Ответ 3

У меня была такая же проблема. В представлении таблицы было несколько разных классов ячеек, каждая из которых была другой высоты. Более того, один из классов ячеек должен был показать дополнительный текст, что означает дальнейшее изменение.

Прокрутка была идеальной в большинстве ситуаций. Однако та же проблема, о которой говорится в вопросе, проявилась. То есть, выбрав ячейку таблицы и представив другой контроллер представления, вернувшись к исходному представлению таблицы, прокрутка вверх была чрезвычайно резкой.

Первая строка исследования состояла в том, чтобы рассмотреть вопрос о том, почему данные перезагружались вообще. Экспериментировав, я могу подтвердить, что при возврате к представлению таблицы данные перезагружаются, хотя не используются reloadData.

Смотрите мой комментарий ios 8 tableview перезагружается автоматически, когда вид появляется после поп

Не имея механизма для деактивации этого поведения, следующая линия подхода заключалась в том, чтобы исследовать отрывную прокрутку.

Я пришел к выводу, что оценки, возвращаемые estimatedHeightForRowAtIndexPath, являются оценочным предварительным вычислением. Зарегистрируйтесь, чтобы отключить оценки, и вы увидите, что метод делегата запрашивается для каждой строки, когда сначала открывается представление таблицы. Это до любой прокрутки.

Я быстро обнаружил, что некоторая логика оценки высоты в моем коде была плохо ошибочной. Устранение этого зафиксировало худшее из раздражения.

Чтобы достичь идеальной прокрутки, я принял несколько иной подход к ответам выше. Высота была кеширована, но используемые значения были от фактических высот, которые были бы захвачены, когда пользователь прокручивает вниз:

    var myRowHeightEstimateCache = [String:CGFloat]()

Чтобы сохранить:

func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    myRowHeightEstimateCache["\(indexPath.row)"] = CGRectGetHeight(cell.frame)
}

Использование в кеше:

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
    if let height = myRowHeightEstimateCache["\(indexPath.row)"]
    {
        return height
    } 
    else 
    {
    // Not in cache
    ... try to figure out estimate 
    }

Обратите внимание, что в вышеприведенном методе вам нужно будет вернуть некоторую оценку, так как этот метод, конечно, будет вызываться до didEndDisplayingCell.

Я предполагаю, что во всем этом есть какая-то ошибка Apple. Вот почему эта проблема проявляется только в сценарии выхода.

Суть в том, что это решение очень похоже на те, что были выше. Однако я избегаю любых сложных вычислений и использую поведение UITableViewAutomaticDimension, чтобы просто кэшировать фактические высоты строк, отображаемые с помощью didEndDisplayingCell.

TL;DR: работайте над наиболее вероятным дефектом UIKit, кэшируя фактические высоты строк. Затем запросите свой кеш в качестве первого параметра метода оценки.

Ответ 4

Хорошо, пока он не будет работать, вы можете удалить эти две строки:

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

И добавьте этот метод в свой 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
}

Он работал без каких-либо изменений в вашем тестовом проекте.

Ответ 5

Если вы установили свойство tableView estimatedRowHeight.

tableView.estimatedRowHeight = 100;

Затем прокомментируйте это.

//  tableView.estimatedRowHeight = 100;

Он решил ошибку, которая встречается в iOS8.1 для меня.

Если вы действительно хотите его сохранить, вы можете заставить tableView перезагрузить Data перед нажатием.

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

или сделать это в viewWillDisappear:.

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

Надеюсь, что это поможет.

Ответ 6

В xcode 6 final для меня обходной путь не работает. Я использую пользовательские ячейки и обводку ячейки в heightForCell приводит к бесконечной петле. По мере того, как ячейка вызывает вызовы heightForCell.

И все же ошибка кажется присутствующей.

Ответ 7

Если ни одно из вышеперечисленных действий не сработало для вас (как это случилось со мной), просто проверьте свойство estimatedRowHeight в представлении таблицы. Я проверил, что использовал 50 пикселей, когда он был на самом деле ближе к 150 пикселям. Обновление этого значения устраняет проблему!

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = tableViewEstimatedRowHeight // This should be accurate.