Mengapa UITableView kehilangan jejak tajuk bagian saat menggulir dan memperbarui secara bersamaan?

Saya mengalami bug di mana tampilan tabel mungkin kehilangan jejak header bagian, sehingga membuat header bagian baru sebagai gantinya. (Lihat gambar berikut; cuplikan layar kanan menunjukkan header yang hilang.)

masukkan deskripsi gambar di sini

Meskipun saya tidak tahu penyebab pastinya, hal ini sepertinya terjadi ketika saya menggulir tampilan tabel secara terprogram dan memperbaruinya secara bersamaan dengan memanggil "beginUpdates" dan "endUpdates." Adakah yang bisa menjelaskan mengapa tampilan tabel kehilangan jejak header bagian? Kapan tepatnya hal ini terjadi?

Di bawah ini, saya telah menyediakan kode untuk pengontrol tampilan yang menunjukkan masalah ini. Kode menampilkan tampilan tabel yang model datanya berupa array dari array string. Saat Anda menekan tombol tambah, baris baru ditambahkan ke tabel dan digulir hingga terlihat. Setelah beberapa saat ditambahkan, baris baru dibuat lebih tinggi dari sel lainnya untuk menunjukkan bahwa itu adalah sel yang baru ditambahkan. Untuk mereproduksi bug, tekan tombol "Tambah" beberapa kali lalu gulir ke atas. (Saya mengujinya di simulator iPhone 64-bit 4 inci.)

Kode yang mungkin menyebabkan bug ada pada metode "pressedAddButton:" karena di sanalah saya menggulir dan memperbarui tampilan tabel secara bersamaan. (EDIT: Sebagian besar kode lainnya hanyalah pelat ketel untuk menyiapkan model data dan menyediakan sumber data serta metode delegasi untuk tampilan tabel.)

Catatan: Agar kode dapat dijalankan, letakkan pengontrol tampilan di dalam UINavigationController dan tampilkan pengontrol navigasi:

ABCTestViewController * testViewController = [[ABCTestViewController alloc] initWithNibName:nil bundle:nil];
UINavigationController * navigationController = [[UINavigationController alloc] initWithRootViewController:testViewController];
// Now show the navigation controller.

Di ABCTestViewController.h:

#import <UIKit/UIKit.h>

@interface ABCTestViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>

@property (weak, nonatomic) UITableView * tableView;

// The data model is an array of arrays of strings.
@property (strong, nonatomic) NSMutableArray * dataModel;

// The special row will be shown as being taller than the other rows.
@property (strong, nonatomic) NSIndexPath * indexPathOfSpecialRow;

@end

Di ABCTestViewController.m:

#import "ABCTestViewController.h"

#import <QuartzCore/QuartzCore.h>

@interface ABCTestViewController ()

@end

@implementation ABCTestViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        [self initializeNavigationItem];
        [self initializeDataModel];
    }
    return self;
}

- (void)initializeNavigationItem
{
    self.title = @"Test";

    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Add" style:UIBarButtonItemStylePlain target:self action:@selector(pressedAddButton:)];
}

- (void)initializeDataModel
{
    self->_dataModel = [[NSMutableArray alloc] init];

    // Add strings to section 0.
    NSMutableArray * section0 = [[self class] stringsForSection:0 numberOfStrings:2];
    [self.dataModel addObject:section0];

    // Add strings to section 1.
    NSMutableArray * section1 = [[self class] stringsForSection:1 numberOfStrings:10];
    [self.dataModel addObject:section1];
}

// Utility.
+ (NSMutableArray *)stringsForSection:(NSUInteger)section numberOfStrings:(NSUInteger)numberOfStrings
{
    NSMutableArray * strings = [[NSMutableArray alloc] initWithCapacity:numberOfStrings];
    for (NSUInteger i = 0; i < numberOfStrings; ++i) {
        NSString * string = [NSString stringWithFormat:@"%d-%d", section, i];
        [strings addObject:string];
    }
    return strings;
}

- (void)loadView
{
    [super loadView];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Add a table view.
    UITableView * tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    self.tableView = tableView;
    self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:self.tableView];

    // Configure the table view.
    self.tableView.sectionHeaderHeight = 40;
    self.tableView.allowsSelection = NO;

    self.tableView.dataSource = self;
    self.tableView.delegate = self;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

// ---------------------

// Event Handers

- (void)pressedAddButton:(id)sender
{
    // Update data model: add a new string to the second section.
    NSUInteger section = 1;
    NSMutableArray * strings = [self.dataModel objectAtIndex:section];
    NSUInteger newStringIndex = strings.count;
    NSString * newString = [NSString stringWithFormat:@"1-%d (New String)", newStringIndex];
    [strings insertObject:newString atIndex:newStringIndex];

    // Update display: add a new row in the table for the newly added string.
    NSIndexPath * indexPath = [NSIndexPath indexPathForRow:newStringIndex inSection:section];
    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

    // Scroll to the newly added row.
    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];

    // After a brief pause, show that the newly added row is "special" by making its height taller than the other rows.
    double delayInSeconds = 0.1;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        // Update display model: set the special row to be the newly added row.
        self.indexPathOfSpecialRow = indexPath;

        // Update display: tell the table view to update the heights of the rows
        // (i.e., tableView:heightForRowAtIndexPath: will be called for each row).
        [self.tableView beginUpdates];
        [self.tableView endUpdates];
    });
}

// ---------------------

// Table View Data Source and Delegate Methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return self.dataModel.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSArray * strings = [self.dataModel objectAtIndex:section];

    return strings.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Data from the data model.
    NSArray * strings = [self.dataModel objectAtIndex:indexPath.section];
    NSString * string = [strings objectAtIndex:indexPath.row];

    static NSString * const kCellIdentifier = @"Cell";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier];
    }

    cell.textLabel.text = string;

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.indexPathOfSpecialRow != nil && [indexPath isEqual:self.indexPathOfSpecialRow]) {
        return tableView.rowHeight * 1.5;
    }
    else {
        return tableView.rowHeight;
    }
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    static NSString * const kHeaderIdentifier = @"Header";
    UITableViewHeaderFooterView * headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kHeaderIdentifier];
    if (headerView == nil) {
        headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:kHeaderIdentifier];

        // Give the header view a border (need to link to QuartzCore.framework to access layer property of UIView).
        headerView.contentView.layer.borderColor = [UIColor colorWithWhite:0 alpha:0.5].CGColor;
        headerView.contentView.layer.borderWidth = 1;
    }

    headerView.textLabel.text = [NSString stringWithFormat:@"Section %d", section];

    return headerView;
}

@end

person platypus    schedule 26.10.2013    source sumber
comment
apakah Anda melihat masalah ini di iOS 6?   -  person Kunal Balani    schedule 27.10.2013
comment
tidak, saya tidak mencobanya di iOS6.   -  person platypus    schedule 27.10.2013
comment
Tampaknya tidak ada yang salah dengan kode Anda kecuali Anda perlu memanggil BeginUpdate dan EndUpdate sebelum dan sesudah insertRowsAtIndexPaths. Tampaknya ada bug Apple di mana mereka menambahkan Subview saat Anda memasukkan baris baru.   -  person Kunal Balani    schedule 28.10.2013
comment
Apakah Anda sudah menemukan sesuatu? Saya memiliki masalah yang sama. Tapi saya bahkan tidak menggulir tabel secara terprogram? Apakah ini bug iOS 7?   -  person Bagusflyer    schedule 06.11.2013
comment
buat if ini headerView atau FooterVIew, lalu atur.   -  person theShay    schedule 25.11.2013


Jawaban (3)


Saya tahu jawaban saya terlambat, tetapi saya mempunyai masalah yang sama. Semua animasi sistem oleh IOS dibuat oleh CATransaction. Solusi saya adalah:

[CATransaction begin];

[CATransaction setCompletionBlock:^{
    //scroll view here
}];

[self.tableView beginUpdates];
[self.tableView endUpdates];

[CATransaction commit];
person oks_ios    schedule 05.01.2014
comment
Bagi saya itu terjadi setiap kali memuat ulang bagian atau baris, bahkan menyisipkan atau menghapus. Jadi ini sangat membantu - Saya memuat ulang seluruh tabel ketika animasi selesai. Aneh sekali, tapi berhasil. Terimakasih - person alexey.metelkin; 30.01.2014
comment
Itu juga terjadi pada saya, solusi yang sangat bagus, Tapi saya ingin tahu apa yang menyebabkan masalah ini :/ - person otakuProgrammer; 17.07.2014
comment
@otakuProgrammer lihat, di iOS, semua animasi harus dilakukan di thread utama, dan hanya satu per satu, jika dua animasi akan ditumpuk dalam satu waktu, dan animasi ini BUKAN transaksi animasi Inti - mereka tidak akan dimulai, semua tampilan akan menjadi disetel ke status selesai segera, tanpa animasi. - person oks_ios; 17.07.2014

Coba ini

// Scroll to the newly added row.
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];

Dibandingkan

[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(loadFetchWithDelay) userInfo:nil repeats:NO];

- (void)loadFetchWithDelay
{    
    self.indexPathOfSpecialRow = indexPath;
    [self.tableView reloadData];
}
person Dimentar    schedule 06.12.2013

perbarui metode di bawah ini mungkin bisa membantu:

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


   NSString *DataCellId = [NSString stringWithFormat:@"Photo%d%d",indexPath.row,indexPath.section];
        cell = (cell *)[tableView dequeueReusableCellWithIdentifier:DataCellId];
        if(!cell) {
            cell = [[cell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:DataCellId];

        }
   // Data from the data model.
    NSArray * strings = [self.dataModel objectAtIndex:indexPath.section];
    NSString * string = [strings objectAtIndex:indexPath.row];

    cell.textLabel.text = string;

    return cell;
}
person Rigel Networks    schedule 06.12.2013