UITableViewCell увеличивает размер высоты в iOS 10
My UITableViewCell
будет анимировать его высоту при распознавании крана. В iOS 9 и ниже эта анимация гладкая и работает без проблем. В iOS 10 beta есть резкий прыжок во время анимации. Есть ли способ исправить это?
Вот базовый пример кода.
- (void)cellTapped {
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.shouldExpandCell ? 200.0f : 100.0f;
}
РЕДАКТИРОВАТЬ: 9/8/16
Проблема все еще существует в GM. После большей отладки я обнаружил, что проблема связана с тем, что ячейка сразу перескакивает на новую высоту и затем оживляет смещение соответствующих ячеек. Это означает, что любая анимация на основе CGRect
, которая зависит от дна ячеек, не будет работать.
Например, если у меня есть представление, ограниченное дном ячеек, оно будет прыгать. Решение будет связано с ограничением сверху с динамической константой. Или подумайте о другом способе позиционирования ваших взглядов, а затем связанных с нижней.
Ответы
Ответ 1
Лучшее решение:
Проблема заключается в том, что UITableView
изменяет высоту ячейки, скорее всего, от -beginUpdates
и -endUpdates
. До iOS 10 анимация в ячейке будет иметь место как для size
, так и для origin
. Теперь, в iOS 10 GM, ячейка немедленно изменится на новую высоту и затем будет анимировать до правильного смещения.
Решение довольно просто с ограничениями. Создайте ограничение для руководства, которое будет обновлять его высоту и иметь другие представления, которые должны быть ограничены в нижней части ячейки, теперь ограниченные этим руководством.
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
UIView *heightGuide = [[UIView alloc] init];
heightGuide.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:heightGuide];
[heightGuide addConstraint:({
self.heightGuideConstraint = [NSLayoutConstraint constraintWithItem:heightGuide attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:0.0f];
})];
[self.contentView addConstraint:({
[NSLayoutConstraint constraintWithItem:heightGuide attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f];
})];
UIView *anotherView = [[UIView alloc] init];
anotherView.translatesAutoresizingMaskIntoConstraints = NO;
anotherView.backgroundColor = [UIColor redColor];
[self.contentView addSubview:anotherView];
[anotherView addConstraint:({
[NSLayoutConstraint constraintWithItem:anotherView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:20.0f];
})];
[self.contentView addConstraint:({
[NSLayoutConstraint constraintWithItem:anotherView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f];
})];
[self.contentView addConstraint:({
// This is our constraint that used to be attached to self.contentView
[NSLayoutConstraint constraintWithItem:anotherView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:heightGuide attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f];
})];
[self.contentView addConstraint:({
[NSLayoutConstraint constraintWithItem:anotherView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeRight multiplier:1.0f constant:0.0f];
})];
}
return self;
}
Затем при необходимости обновите высоту направляющих.
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
if (self.window) {
[UIView animateWithDuration:0.3 animations:^{
self.heightGuideConstraint.constant = frame.size.height;
[self.contentView layoutIfNeeded];
}];
} else {
self.heightGuideConstraint.constant = frame.size.height;
}
}
Обратите внимание, что размещение руководства по обновлению в -setFrame:
может оказаться не лучшим местом. На данный момент я только создал этот демо-код для создания решения. Как только я закончу обновление своего кода с окончательным решением, если я найду лучшее место для его размещения, я отредактирую.
Исходный ответ:
Когда близится к завершению бета-версия iOS 10, мы надеемся, что эта проблема будет решена в следующей версии. Также есть отчет об ошибке.
Мое решение включает удаление слоя CAAnimation
. Это обнаружит изменение высоты и автоматически оживит, подобно использованию CALayer
, не привязанного к UIView
.
Первым шагом является настройка того, что происходит, когда слой обнаруживает изменение. Этот код должен находиться в подклассе вашего представления. Это представление должно быть подчиненным вашим tableViewCell.contentView
. В коде мы проверяем, имеет ли свойство действия слоя представления ключ нашей анимации. Если не просто вызов супер.
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
return [layer.actions objectForKey:event] ?: [super actionForLayer:layer forKey:event];
}
Затем вы хотите добавить анимацию в свойство actions. Вы можете обнаружить, что этот код лучше всего применять после того, как представление находится в окне и выложено. Применять его заранее может привести к анимации, когда представление переместится в окно.
- (void)didMoveToWindow {
[super didMoveToWindow];
if (self.window) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
self.layer.actions = @{animation.keyPath:animation};
}
}
И это! Не нужно применять animation.duration
, поскольку представление таблицы -beginUpdates
и -endUpdates
отменяет его. В общем, если вы используете этот трюк как простой способ применения анимации, вам нужно добавить animation.duration
и, возможно, также animation.timingFunction
.
Ответ 2
Решение для iOS 10 в Swift с автоматической компоновкой:
Поместите этот код в свой пользовательский UITableViewCell
override func layoutSubviews() {
super.layoutSubviews()
if #available(iOS 10, *) {
UIView.animateWithDuration(0.3) { self.contentView.layoutIfNeeded() }
}
}
В Objective-C:
- (void)layoutSubviews
{
[super layoutSubviews];
NSOperatingSystemVersion ios10 = (NSOperatingSystemVersion){10, 0, 0};
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:ios10]) {
[UIView animateWithDuration:0.3
animations:^{
[self.contentView layoutIfNeeded];
}];
}
}
Это изменит высоту изменения UITableViewCell, если вы настроили UITableViewDelegate, как показано ниже:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return selectedIndexes.contains(indexPath.row) ? Constants.expandedTableViewCellHeight : Constants.collapsedTableViewCellHeight
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
tableView.beginUpdates()
if let index = selectedIndexes.indexOf(indexPath.row) {
selectedIndexes.removeAtIndex(index)
} else {
selectedIndexes.append(indexPath.row)
}
tableView.endUpdates()
}
Ответ 3
Я не использую автоматический макет, вместо этого я получаю класс из UITableViewCell и использую его "layoutSubviews" для программного программирования фреймов подпрограмм. Поэтому я попытался изменить ответ cnotethegr8, чтобы он соответствовал моим потребностям, и я придумал следующее.
Повторите "setFrame" ячейки, установите высоту содержимого ячейки на высоту ячейки и вызовите ретрансляцию субвью в блоке анимации:
@interface MyTableViewCell : UITableViewCell
...
@end
@implementation MyTableViewCell
...
- (void) setFrame:(CGRect)frame
{
[super setFrame:frame];
if (self.window)
{
[UIView animateWithDuration:0.3 animations:^{
self.contentView.frame = CGRectMake(
self.contentView.frame.origin.x,
self.contentView.frame.origin.y,
self.contentView.frame.size.width,
frame.size.height);
[self setNeedsLayout];
[self layoutIfNeeded];
}];
}
}
...
@end
Это сделал трюк:)
Ответ 4
Столкнувшись с той же проблемой на iOS 10 (все было в порядке на iOS 9), решение заключалось в предоставлении фактического оценочногоRowHeight и heightForRow с помощью реализации методов UITableViewDelegate.
Поля ячейки, позиционированные с помощью AutoLayout, поэтому я использовал UITableViewAutomaticDimension для расчета высоты (вы можете попробовать установить его как свойство tableView rowHeight).
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat result = kDefaultTableViewRowHeight;
if (isAnimatingHeightCellIndexPAth) {
result = [self precalculatedEstimatedHeight];
}
return result;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
Ответ 5
Итак, у меня была эта проблема для UITableViewCell
, которая использовала ограничения макета и UITableView
, которые использовали автоматическую высоту ячеек (UITableViewAutomaticDimension
). Поэтому я не уверен, сколько из этого решения будет работать для вас, но я помещаю его здесь, поскольку он тесно связан.
Основная идея заключалась в том, чтобы снизить приоритет ограничений, вызывающих изменение высоты:
self.collapsedConstraint = self.expandingContainer.topAnchor.constraintEqualToAnchor(self.bottomAnchor).priority(UILayoutPriorityDefaultLow)
self.expandedConstraint = self.expandingContainer.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).priority(UILayoutPriorityDefaultLow)
self.expandedConstraint?.active = self.expanded
self.collapsedConstraint?.active = !self.expanded
Во-вторых, способ, которым я анимировал табличное представление, был очень специфичным:
UIView.animateWithDuration(0.3) {
let tableViewCell = tableView.cellForRowAtIndexPath(viewModel.index) as! MyTableViewCell
tableViewCell.toggleExpandedConstraints()
tableView.beginUpdates()
tableView.endUpdates()
tableViewCell.layoutIfNeeded()
}
Обратите внимание, что layoutIfNeeded()
должен был прийти после beginUpdates
/endUpdates
Я думаю, что эта последняя часть может быть вам полезной....
Ответ 6
Невозможно прокомментировать из-за репутации, но решение sam работало для меня на IOS 10.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EWGDownloadTableViewCell *cell = (EWGDownloadTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
if (self.expandedRow == indexPath.row) {
self.expandedRow = -1;
[UIView animateWithDuration:0.3 animations:^{
cell.constraintToAnimate.constant = 51;
[tableView beginUpdates];
[tableView endUpdates];
[cell layoutIfNeeded];
} completion:nil];
} else {
self.expandedRow = indexPath.row;
[UIView animateWithDuration:0.3 animations:^{
cell.constraintToAnimate.constant = 100;
[tableView beginUpdates];
[tableView endUpdates];
[cell layoutIfNeeded];
} completion:nil];
}
}
где constraintToAnimate - это ограничение по высоте на подвью, добавленное в содержимое contentView.