我们看到有些程序很明显是在拖动的为止之后才加载数据:
1. APP store的模式,使用下一个25项目,边上的也是scrollview的scrollbar,不象是tableview自带的,可以猜测是用了lableview(custom view)+scrollview来实现的;困难就在于动态判断行是不是在可视区域:
经过我不停地点《下面25项》后达到了300个项目在一个view上,而且滚动浏览的效果还不错。真的是要水平的。
2. 作为tableview,据说行数多余300后效果就不好了
|
|
搞一个key index array,只有在visible的时候才去加载数据到内存中,这个方法靠谱?
|
b。然后从本地先绑定到view:如果联网,数据库操作应该也很快的。。。
c。根据row的url各种,去下载图片/概要文件/pdf文件
4. 返回null/空白cell的方式,但没想好怎么和scroll 关联。。。
I've instructed memory allocations and call stack using Instruments during opening section events. It showed me, that the majority of time is spent on loading cell from nib file.
Firstly, that I've done was reducing the size of nib file, i.e. minimizing the number of views used in custom tableview cell (now its only 2 views and 2 labels, instead of 6 views, 2 images and 2 labels before). It gave me some improve in cells loading. Apple documentation suggests to use as few as possible views and do not use transparency. So be attentive to these suggestions.
Secondly, as I discovered earlier, that not all cell are visible which are created by -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)
, I decided to reduce somehow the number of loadings new cells from nib file. To achieve this, I've came to simple idea: return blank default cells for invisible rows, while load custom cells from nib for visible ones. Here is the piece of code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self index:indexPath isInvisibleInTableView:tableView])
return [self getBlankCellForTableView:tableView];
// the rest of the method is the same
...
}
-(BOOL)index:(NSIndexPath*)indexPath isInvisibleInTableView:(UITableView*)tableView
{
NSMutableArray *visibleIndexPaths = [self getExtendedVisibleIndexPathsForTableView:tableView];
return ![visibleIndexPaths containsObject:indexPath];
}
-(UITableViewCell*)getBlankCellForTableView:(UITableView*)tableView
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"IVBlankCell"];
if (!cell)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"IVBlankCell"] autorelease];
return cell;
}
As you can see, I'm not using just -(NSArray*)indexPathsForVisibleRows
method of tableview for detecting visible cells. Instead, I've wrote my own method -(NSMutableArray*)getExtendedVisibleIndexPathsForTableView:(UITableView*)tableView
. It was necessary because for some reason, when using -(NSArray*)indexPathsForVisibleRows
the cells that are next to the last one visible cell or the cells that are previous to the first one visible cell were created as blank cells and looked like empty cells while scrolling. To overcome this, in -(NSMutableArray*)getExtendedVisibleIndexPathsForTableView: (UITableView*)tableView
i'm adding border cells to the visible array cells:
-(NSMutableArray*)getExtendedVisibleIndexPathsForTableView:(UITableView*)tableView{
NSArray *visibleIPs = [tableView indexPathsForVisibleRows];
if (!visibleIPs || ![visibleIPs count])
return [NSMutableArray array];
NSIndexPath *firstVisibleIP = [visibleIPs objectAtIndex:0];
NSIndexPath *lastVisibleIP = [visibleIPs objectAtIndex:[visibleIPs count]-1];
NSIndexPath *prevIndex = ([firstVisibleIP row])?[NSIndexPath indexPathForRow:[firstVisibleIP row]-1 inSection:[firstVisibleIP section]]:nil;
NSIndexPath *nextIndex = [NSIndexPath indexPathForRow:[lastVisibleIP row]+1 inSection:[lastVisibleIP section]];
NSMutableArray *exVisibleIndexPaths = [NSMutableArray arrayWithArray:[tableView indexPathsForVisibleRows]];
if (prevIndex)
[exVisibleIndexPaths addObject:prevIndex];
[exVisibleIndexPaths addObject:nextIndex];
return exVisibleIndexPaths;
}
Thereby, I've reduced the time of opening sections with large number of custom cells, which was proved by Instruments tracing and felt while experiencing the app.
5. 使用paging的tableviw方式
As you can see from the screenshot, we're going to add the pagination bar in the UITableView's footer. In the pagination bar, we're going to add a text label to show current page number, a Previous button and a Next button. The two buttons are set target to "nextPage" method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
- (UIView *) paginationView {
UIView *paginationView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320, 40.0)];
//create Current Page No Label
UILabel *pageLabel = [[UILabel alloc] initWithFrame:CGRectMake(140, 5, 70, 25)];
pageLabel.text= [@
"page "
stringByAppendingFormat:@
"%d"
,pageNo];
pageLabel.tag=PAGE_LABEL_TAG;
[paginationView addSubview:pageLabel];
//create Previous button
UIButton *preButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
preButton.frame = CGRectMake(20, 5,60, 25);
[preButton setTitle:@
"Pre"
forState:UIControlStateNormal];
[preButton addTarget: self action: @selector(nextPage:)
forControlEvents: UIControlEventTouchUpInside];
preButton.tag = PRE_BUTTON_TAG;
//hide Pre button in the first page
if
(self.pageNo==1) {
preButton.hidden = YES;
}
[paginationView addSubview:preButton];
//create next button
UIButton *nextButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
nextButton.frame = CGRectMake(240, 5,60, 25);
[nextButton setTitle:@
"Next"
forState:UIControlStateNormal];
/* Prepare target-action */
[nextButton addTarget: self action: @selector(nextPage:)
forControlEvents: UIControlEventTouchUpInside];
nextButton.tag = NEXT_BUTTON_TAG;
[paginationView addSubview:nextButton];
return
paginationView;
}
|
Then add pagination bar into footer:
1
2
3
4
5
6
7
8
|
- (
void
)viewWillAppear:(
BOOL
)animated
{
UITableView *tableView = (UITableView *) self.view;
tableView.tableFooterView = [self paginationView];
[super viewWillAppear:animated];
}
|
The last part is the nextPage method, where we will load data and refresh the table view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
-(
void
) nextPage:(UIButton *)button{
if
(button.tag==NEXT_BUTTON_TAG) {
self.pageNo= self.pageNo+1;
}
else
{
self.pageNo= self.pageNo-1;
}
UIButton *preButton = (UIButton *)[self.view viewWithTag:PRE_BUTTON_TAG];
//hide Pre button in the first page
if
(self.pageNo<=1) {
preButton.hidden = YES;
}
else
{
//unhide preButton
preButton.hidden = NO;
}
UILabel *pageLabel = (UILabel *)[self.view viewWithTag:PAGE_LABEL_TAG];
pageLabel.text= [@
"page "
stringByAppendingFormat:@
"%d"
,pageNo];
//[pageLabel setText:<#(NSString *)#>
PaginationData *pageData = [[PaginationData alloc] init];
self.data = [pageData dataInPage:self.pageNo];
//[pageData release];
//reload data and scroll to the first record
[self.tableView reloadData];
[self.tableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
}
|
UPDATE about PaginationData:
PaginationData is just my mock data to hold items for each page. It's nonsense for you actually. However, I will show you here since some people interested.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
#import "PaginationData.h"
@implementation PaginationData
-(id) init{
self = [super init];
if
(self) {
data = [[NSMutableArray alloc] init];
}
return
self;
}
-(NSArray *) dataInPage:(NSInteger)pageNo {
for
(
int
i=1;i<=10;i++){
NSInteger itemNo = (pageNo-1)*10+i;
NSString *line = [[NSString alloc] initWithFormat:@
"This is test item %d"
, itemNo];
[data addObject:line];
}
return
data;
}
- (
void
) dealloc{
[data release];
[super dealloc];
}
@end
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(tableView == table1){
return 10;
} else {
return 5;
}
}
7.有图的时候如何加速
预渲染图像。
你会发现即使做到了上述几点,当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕,详细做法可见《利用预渲染加速iOS设备的图像显示》。
8,scroll关联的一些操作:NSOperationQueue
当然,在不需要响应用户请求时,也可以增加下载线程数,以加快下载速度:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
queue.maxConcurrentOperationCount = 5;
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
queue.maxConcurrentOperationCount = 2;
}
此外,自动载入更新数据对用户来说也很友好,这减少了用户等待下载的时间。例如每次载入50条信息,那就可以在滚动到倒数第10条以内时,加载更多信息:- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (count - indexPath.row < 10 && !updating) {
updating = YES;
[self update];
}
}
// update方法获取到结果后,设置updating为NO
还有一点要注意的就是当图片下载完成后,如果cell是可见的,还需要更新图像:
NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *visibleIndexPath in indexPaths) {
if (indexPath == visibleIndexPath) {
MyTableViewCell *cell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
cell.image = image;
[cell setNeedsDisplayInRect:imageRect];
break;
}
}
// 也可不遍历,直接与头尾相比较,看是否在中间即可。
最后还是前面所说过的insertRowsAtIndexPaths:withRowAnimation:方法,插入新行需要在主线程执行,而一次插入很多行的话(例如50行),会长时间阻塞主线程。而换成reloadData方法的话,瞬间就处理完了。
9. 使用scroll相关的scrollViewDidEndDragging
// this method is used in case the user scrolled into a set of cells that don't have their app icons yet
- (void)loadImagesForOnscreenRows
{
if ([self.entries count] > 0)
{
NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *indexPath in visiblePaths)
{
AppRecord *appRecord = [self.entries objectAtIndex:indexPath.row];
if (!appRecord.appIcon) // avoid the app icon download if the app already has an icon
{
[self startIconDownload:appRecord forIndexPath:indexPath];
}
}
}
}
// called by our ImageDownloader when an icon is ready to be displayed
- (void)appImageDidLoad:(NSIndexPath *)indexPath
{
IconDownloader *iconDownloader = [imageDownloadsInProgress objectForKey:indexPath];
if (iconDownloader != nil)
{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:iconDownloader.indexPathInTableView];
// Display the newly loaded image
cell.imageView.image = iconDownloader.appRecord.appIcon;
}
}
#pragma mark -
#pragma mark Deferred image loading (UIScrollViewDelegate)
// Load images for all onscreen rows when scrolling is finished
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// YES if the scrolling movement will continue。。。。。
if (!decelerate)
{
[self loadImagesForOnscreenRows];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self loadImagesForOnscreenRows];
}
// call our delegate and tell it that our icon is ready for display
[delegate appImageDidLoad:self.indexPathInTableView];
--@protocol IconDownloaderDelegate
- (void)appImageDidLoad:(NSIndexPath *)indexPath;
@end