2012年8月29日 星期三

iOS 開發筆記 - UITableViewController 之 dataSource 更新顯示

最近開始複習 iOS SDK,過去使用 UITableViewController 時,每次資料新增變動時,都是透過 [self.tableView reloadData] 來更新畫面,效果就是把整個 UITableView 都重畫一次,只是若碰到資料更新很頻繁時,就會發現有些 touch event 因過度 reloadData 而出現短暫不能被處理。最近看 UITableView 相關書籍時,才發現原來早在 iOS SDK 3.0 時,就可以指定某個 Section 的某個 Row 更新就好!這招當然就可以用在新增資料及更新資料,實在太讚了!不曉得是不是太早接觸 iPhone SDK 哩,真的是太晚發現了 Orz


From UITableView Class Reference



  • reloadData (iOS SDK 2.0)

  • reloadRowsAtIndexPaths:withRowAnimation: (iOS SDK 3.0)

  • reloadSections:withRowAnimation: (iOS SDK 3.0)

  • reloadSectionIndexTitles (iOS SDK 3.0)


早期寫法(iOS 2.0):



  1. 更新 dataSource

  2. 呼叫 [self.tableView reloadData]


更有效率得寫法(iOS 3.0),新增資料(前頭):



  1. [dataSource insertObject:newItem atIndex:0];

  2. [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];


更有效率得寫法(iOS 3.0),新增資料(後頭):



  1. [dataSource addObject:newItem];

  2. [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:[dataSource count]-1 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];


如果,不幸一開始使用卻會蹦出 Exception:


Invalid update: invalid number of sections.  The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).


那可能跟我的情況很像,簡單的說,假設你的 numberOfSectionsInTableView 判斷是 return dataSource && [dataSource count] ? 1 : 0 ; 的話,那一開始因為沒資料而 sections 個數為 0,所以更新指定 section 時會出錯,解法有兩個,一個是一開始 numberOfSectionsInTableView 就會回傳固定數值(非0),這樣就不會出錯;另一個則是第一次新增資料時,呼叫 [self.tableView reloadData] 處理,之後才用 insertRowsAtIndexPaths 處理:


[dataSource addObject:item];
if( [dataSource count] == 1 )
        [self.tableView reloadData];
else
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:[dataSource count]-1 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];


此外,在新增資料在前頭後,如果更新太頻繁時將會碰到 'NSInternalInconsistencyException', reason: 'Attempt to create two animations for cell' 而程式 crash,例如:


for( int i=0 ; i<50 ; i++ ) {
         [dataSource insertObject:[NSString stringWithFormat:@"%d",i] atIndex:0]; // 每次塞在 index = 0 的位置
         [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic]; // 每次都對 index = 0 的 cell 更新
}


解法:


for( int i=0 ; i<50 ; i++ ) {
        [dataSource insertObject:[NSString stringWithFormat:@"%d",i] atIndex:0];
        [self.tableView beginUpdates];
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
        [self.tableView endUpdates];
}


或統一更新:


NSMutableArray *indexPathSet = [[NSMutableArray alloc] init];
for( int i=0 ; i<50 ; i++ ) {
        [dataSource insertObject:[NSString stringWithFormat:@"%d",i] atIndex:0];
        [indexPathSet addObject:[NSIndexPath indexPathForRow:i inSection:0]]; // 依序累積 50 個 rows
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPathSet withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
[indexPathSet release];


故比較好得方式就是在對 cell 處理更新特效時(除了reloadData用法外),一律採用 [self.tableView beginUpdates]; 和 [self.tableView endUpdates]; 包起來(有點lock味道),並且在目前很流行 Event-Driven 或 Async 架構更須如此。更多詳情,請參考 UITableView Class Reference 囉。


沒有留言:

張貼留言