关于tableview的滚动后动态加载数据

来源:互联网 发布:网络 全球 编辑:程序博客网 时间:2024/05/17 03:39

我们看到有些程序很明显是在拖动的为止之后才加载数据:

1. APP store的模式,使用下一个25项目,边上的也是scrollview的scrollbar,不象是tableview自带的,可以猜测是用了lableview(custom view)+scrollview来实现的;困难就在于动态判断行是不是在可视区域:


经过我不停地点《下面25项》后达到了300个项目在一个view上,而且滚动浏览的效果还不错。真的是要水平的。


2. 作为tableview,据说行数多余300后效果就不好了

UITableView is very inefficient for records more than 200-300. I have been there, believe me. Think of something else.
    所以有一种建议:使用uiscrollview和uilabels

 Consider for example using UIScrollView with UILabels put inside it as your cells.
3.考虑到内存,尤其iphone的app才多少M就很慢了。

搞一个key index array,只有在visible的时候才去加载数据到内存中,这个方法靠谱?

create an array and store just the primary key of the records from your SELECT statement. Then, you basically have an integer array. What you do is on every cell, simply load the cell with the data from the primary key.
 a。总之是要把数据存在到本地先

 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*)indexPathsForVisibleRowsthe 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];
     
    returnpaginationView;
}

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];   
    }
    returnself;
     
}
 
-(NSArray *) dataInPage:(NSInteger)pageNo {
 
     
    for(inti=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
实现两栏的tableview,其实是两个tableview 并排在一起


- (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];

}

===》通过delegate来实现 调用的,在downloader类中有如下的代码:
- -

 // call our delegate and tell it that our icon is ready for display

    [delegate appImageDidLoad:self.indexPathInTableView];

--

@protocol IconDownloaderDelegate 


- (void)appImageDidLoad:(NSIndexPath *)indexPath;


@end




  


原创粉丝点击