Сохранять uitableview static при вставке строк вверху

У меня есть tableView, в который я вставляю строки вверху.

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

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

Есть ли хороший способ достичь этого?

Обновление: я использую beginUpdate, затем insertRowsAtIndexPath, endUpdates. Нет вызова reloadData.

scrollToRowAtIndexPath переходит в верхнюю часть текущей ячейки (сохраняется перед добавлением строк).

Другой подход, который я пробовал, который в точности соответствует точно, но с дрожанием.

save tableView currentOffset. (Underlying scrollView method)
Add rows (beginUpdates,insert...,endUpdates) 
reloadData ( to force a recalulation of the scrollview size )
Recalculate the correct new offset from the bottom of the scrollview
setContentOffset (Underlying scrollview method)

Неисправность - это reloadData заставляет scrollview/tableview начинать прокрутку ненадолго, а setContentOffset возвращает его в нужное место.

Есть ли способ заставить tableView выработать новый размер без запуска?

Обертка всего этого в beginAnimation commitAnimation тоже не помогает.

Обновление 2: это может быть сделано ясно - см. приложение для оффлайнового твиттера для одного, когда вы выходите для обновления.

Ответ 1

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

    ...
    CGSize beforeContentSize = self.tableView.contentSize;
    [self.tableView reloadData];
    CGSize afterContentSize = self.tableView.contentSize;

    CGPoint afterContentOffset = self.tableView.contentOffset;
    CGPoint newContentOffset = CGPointMake(afterContentOffset.x, afterContentOffset.y + afterContentSize.height - beforeContentSize.height);
    self.tableView.contentOffset = newContentOffset;
    ...

Ответ 2

-(void) updateTableWithNewRowCount : (int) rowCount
{    
    //Save the tableview content offset 
    CGPoint tableViewOffset = [self.tableView contentOffset];                                                                                                            

    //Turn of animations for the update block 
    //to get the effect of adding rows on top of TableView
    [UIView setAnimationsEnabled:NO];

    [self.tableView beginUpdates];                    

    NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];        

    int heightForNewRows = 0;

    for (NSInteger i = 0; i < rowCount; i++) {

        NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:SECTION_TO_INSERT];
        [rowsInsertIndexPath addObject:tempIndexPath];

        heightForNewRows = heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];                        
    }

    [self.tableView insertRowsAtIndexPaths:rowsInsertIndexPath withRowAnimation:UITableViewRowAnimationNone];                                                                            

    tableViewOffset.y += heightForNewRows;                                                                                 

    [self.tableView endUpdates]; 

    [UIView setAnimationsEnabled:YES];

    [self.tableView setContentOffset:tableViewOffset animated:NO];           
}


-(int) heightForCellAtIndexPath: (NSIndexPath *) indexPath
{

    UITableViewCell *cell =  [self.tableView cellForRowAtIndexPath:indexPath];

    int cellHeight   =  cell.frame.size.height;

    return cellHeight;
}

Просто проведите в строке число новых строк, чтобы вставить вверху.

Ответ 3

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

Один правильный способ: Используйте подкласс UITableView и переопределите -setContentSize: в котором вы можете каким-то образом вычислить, насколько табличное представление нажато и смещено, установив contentOffset.

Это простейший примерный код для обработки простейшей ситуации, когда все вставки происходят в верхней части табличного представления:

@implementation MyTableView

- (void)setContentSize:(CGSize)contentSize {
        // I don't want move the table view during its initial loading of content.
    if (!CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
        if (contentSize.height > self.contentSize.height) {
            CGPoint offset = self.contentOffset;
            offset.y += (contentSize.height - self.contentSize.height);
            self.contentOffset = offset;
        }
    }
    [super setContentSize:contentSize];
}

@end

Ответ 4

имел ту же проблему и нашел решение.

    save tableView currentOffset. (Underlying scrollView method)
    //Add rows (beginUpdates,insert...,endUpdates) // don't do this!
    reloadData ( to force a recalulation of the scrollview size )
    add newly inserted row heights to contentOffset.y here, using tableView:heightForRowAtIndexPath:
    setContentOffset (Underlying scrollview method)

вот так:

- (CGFloat) firstRowHeight
{
    return [self tableView:[self tableView] heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}

...
CGPoint offset = [[self tableView] contentOffset];
[self tableView] reloadData];
offset.y += [self firstRowHeight];
if (offset.y > [[self tableView] contentSize].height) {
    offset.y = 0;
}
[[self tableView] setContentOffset:offset];
...

работает отлично, без сбоев.

Ответ 5

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

static CGPoint  delayOffset = {0.0};

- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller {
    if ( animateChanges )
        [self.tableView beginUpdates];
    delayOffset = self.tableView.contentOffset; // get the current scroll setting
}

Добавлено это в точках вставки клеток. Вы можете сделать вычитание для вычитания соты.

    case NSFetchedResultsChangeInsert:

        delayOffset.y += self.tableView.rowHeight;  // add for each new row
        if ( animateChanges )   
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
        break;

и, наконец,

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    if ( animateChanges )   
    {
        [self.tableView setContentOffset:delayOffset animated:YES];
        [self.tableView endUpdates];
    }
    else
    {
        [self.tableView reloadData];
        [self.tableView setContentOffset:delayOffset animated:NO];
    }
}

С animateChanges = NO, я не мог видеть, что что-то двигалось при добавлении ячеек.

При тестировании с помощью animateChanges = YES, там был "Judder". Кажется, анимация вставки ячеек не имела такой же скорости, что и прокрутка анимированной таблицы. В то время как результат в конце может заканчиваться видимыми ячейками точно там, где они начались, вся таблица, кажется, перемещает 2 или 3 пикселя, а затем возвращается назад.

Если скорость анимации может быть одинаковой, она может оставаться неподвижной.

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

Ответ 6

@Dean,

Вы можете изменить свой код таким образом, чтобы предотвратить анимацию.

[tableView beginUpdates];
[UIView setAnimationsEnabled:NO];

// ...

[tableView endUpdates];

[tableView setContentOffset:newOffset animated:NO];
[UIView setAnimationsEnabled:YES];

Ответ 7

Все любят копировать и вставлять примеры кода, поэтому здесь отвечает реализация ответа Андрея З..

Это в моем методе delegateDidFinishUpdating:(MyDataSourceDelegate*)delegate

if (self.contentOffset.y <= 0)
{
    [self beginUpdates];
    [self insertRowsAtIndexPaths:insertedIndexPaths withRowAnimation:insertAnimation];
    [self endUpdates];
}
else
{
    CGPoint newContentOffset = self.contentOffset;
    [self reloadData];

    for (NSIndexPath *indexPath in insertedIndexPaths)
        newContentOffset.y += [self.delegate tableView:self heightForRowAtIndexPath:indexPath];

    [self setContentOffset:newContentOffset];

    NSLog(@"New data at top of table view");
}

NSLog внизу можно заменить вызовом, чтобы показать представление, в котором указаны свежие данные.

Ответ 8

Я столкнулся с ситуацией, когда есть много разделов, которые могут иметь различное количество строк между вызовами -reloadData из-за пользовательской группировки и высоты строк. Итак, вот решение на базе AndreyZ's. Это contentHeight свойство UIScrollView до и после -reloadData и кажется более универсальным.

CGFloat contentHeight = self.tableView.contentSize.height;
CGPoint offset = self.tableView.contentOffset;
[self.tableView reloadData];

offset.y += (self.tableView.contentSize.height - contentHeight);
if (offset.y > [self.tableView contentSize].height)
    offset.y = 0;

[self.tableView setContentOffset:offset];

Ответ 9

Как вы добавляете строки в таблицу?

Если вы меняете источник данных, а затем вызываете reloadData, это может привести к тому, что таблица снова будет прокручиваться вверх.

Однако, если вы используете методы beginUpdates, insertRowsAtIndexPaths:withRowAnimation:, endUpdates, вы должны иметь возможность вставлять строки без вызова reloadData, сохраняя таблицу в исходном положении.

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

Ответ 10

В конце концов я решил это сделать, показывая текущее представление таблицы в UIImage, а затем помещаю временный UIImageView над табличным представлением, пока он анимируется.

Следующий код будет генерировать изображение


// Save the current tableView as an UIImage
CSize pageSize = [[self tableView] frame].size;
UIGraphicsBeginImageContextWithOptions(pageSize, YES, 0.0); // 0.0 means scale appropriate for device ( retina or no )
CGContextRef resizedContext = UIGraphicsGetCurrentContext();
CGPoint offset = [[self tableView] contentOffset];
CGContextTranslateCTM(resizedContext,-(offset.x),-(offset.y));

[[[self tableView  ]layer] renderInContext:resizedContext];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();    

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

Ответ 11

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

tableView.transform = CGAffineTransformMakeRotation(M_PI);

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

   cell.transform = CGAffineTransformMakeRotation(M_PI);

}

Используйте [tableView setScrollIndicatorInsets:UIEdgeInsetsMake(0, 0, 0, 310)], чтобы установить относительное положение для прокрутки. Он будет с правой стороны после поворота таблицы.

Ответ 12

Просто голова не представляется возможной, если вы вернете оценочные высоты для представления таблицы.

   - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath ;

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

Чтобы заставить его работать, используйте один из приведенных выше ответов (я пошел с ответом @Mayank Yadav), не реализуйте метод measuredHeight и кешируйте высоты ячеек (помня, что настраиваем кеш при вставке дополнительных ячеек наверху).

Ответ 13

Простое решение для отключения анимации

func addNewRows(indexPaths: [NSIndexPath]) {
    let addBlock = { () -> Void in
        self.tableView.beginUpdates()
        self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .None)
        self.tableView.endUpdates()
    }

    tableView.contentOffset.y >= tableView.height() ? UIView.performWithoutAnimation(addBlock) : addBlock()
}

Ответ 14

Основываясь на ответе Andrey Z, вот живой пример, который идеально подходит для меня...

int numberOfRowsBeforeUpdate = [controller.tableView numberOfRowsInSection:0];
CGPoint currentOffset = controller.tableView.contentOffset;

if(numberOfRowsBeforeUpdate>0)
{
    [controller.tableView reloadData];
    int numberOfRowsAfterUpdate = [controller.tableView numberOfRowsInSection:0];

    float rowHeight = [controller getTableViewCellHeight];  //custom method in my controller
    float offset = (numberOfRowsAfterUpdate-numberOfRowsBeforeUpdate)*rowHeight;

    if(offset>0)
    {
        currentOffset.y = currentOffset.y+offset;
        [controller.tableView setContentOffset:currentOffset];
    }
}
else
    [controller.tableView reloadData];

Ответ 15

Поздно, но это работает даже тогда, когда ячейка имеет динамические высоты (aka UITableViewAutomaticDimension), нет необходимости перебирать ячейки для вычисления их размера, но работает только тогда, когда элементы добавляются в самом начале таблицыView и там нет заголовка, с небольшим количеством математики, вероятно, можно адаптировать это ко всем ситуациям:

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row == 0 {
            self.getMoreMessages()
        }
}

private func getMoreMessages(){
        var initialOffset = self.tableView.contentOffset.y
        self.tableView.reloadData()
        //@numberOfCellsAdded: number of items added at top of the table 
        self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: numberOfCellsAdded, inSection: 0), atScrollPosition: .Top, animated: false)
        self.tableView.contentOffset.y += initialOffset
}

Ответ 16

Ответы AmitP, версия Swift 3

let beforeContentSize = self.tableView.contentSize
self.tableView.reloadData()
let afterContentSize = self.tableView.contentSize
let afterContentOffset = self.tableView.contentOffset
let newContentOffset = CGPoint(x: afterContentOffset.x, y: afterContentOffset.y + afterContentSize.height - beforeContentSize.height)
                    self.tableView.contentOffset = newContentOffset

Ответ 17

Как насчет использования scrollToRowAtIndexPath: atScrollPosition: анимированный:? Вы должны просто добавить элемент в свой источник данных, установить строку с указанным выше методом и перезагрузить таблицу...