1. 程式人生 > >IOS開發~UISCrollView與UITableView巢狀使用終極解決方案

IOS開發~UISCrollView與UITableView巢狀使用終極解決方案

問題由來:專案需要做類似網易新聞的那種UIScrollView上放多個UITableView的效果,其中UITableView還要有下拉重新整理效果。

一開始的思路,也是最直觀的思路就是一個UIScrollView上放多個UITableView,然後發現UITableView的滑動和UIScrollView的滑動產生衝突,使用者體驗不好。主要原因在於UIScrollView的滑動原理。

基礎知識看這裡:

http://snorlax.sinaapp.com/?p=178

http://www.devdiv.com/forum.php?mod=viewthread&tid=197496

總結這兩篇,問題在於如果想讓UITableView可以下拉,並且顯示下拉重新整理元件,那麼就不能讓UIScrollView滾動(scrollEnabled=NO),如果想左右滑動顯示並列的其他UITableView,那麼就需要讓UIScrollView可以滾動,但是,怎麼知道使用者想如何操作?所以此路不通。(即使可以滑動,但效果遠遠達不到產品需求)

與是就有了下面的想法:用一個UITableView作為背景,但這個UITableView僅有一個cell,然後在這個cell上放一個橫著的UITableView,然後在這個橫著的UITableView上放N個View,這樣也達到了“UIScrollView上放多個UITableView的效果”。

上程式碼:

背景UITableView:

[cpp] view plaincopyprint?
  1. //實現Table
  2. CGRect scrollRect = CGRectMake(0, 0, 320, 460);  
  3. self.tableBgScroll = [[[UITableView alloc] initWithFrame:scrollRect style:UITableViewStylePlain] autorelease];  
  4. [self.tableBgScroll setDelegate:self];  
  5. [self.tableBgScroll setDataSource:self];  
  6. //Table的資料來源方法
  7. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  
  8. {  
  9.     return 1;  
  10. }  
  11. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  12. {  
  13.     static NSString *cellname = @"cell";  
  14.     InfoCell *cell = (InfoCell *)[tableView dequeueReusableCellWithIdentifier:cellname];  
  15.     if (cell == nil)  
  16.     {  
  17.         cell = [[[InfoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellname]autorelease];  
  18.     }  
  19.     cell.selectionStyle = UITableViewCellSelectionStyleNone;  
  20.     return cell;  
  21. }  
  22. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath  
  23. {  
  24.     return 460;  
  25. }  


InfoCell實現:

[cpp] view plaincopyprint?
  1. #import <UIKit/UIKit.h>
  2. @interface InfoCell : UITableViewCell<UITableViewDataSource, UITableViewDelegate>  
  3. {  
  4.     UITableView *hortable;  
  5. }  
  6. @end  
  7. @implementation InfoCell  
  8. - (void)dealloc  
  9. {  
  10.     [super dealloc];  
  11. }  
  12. - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier  
  13. {  
  14.     self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];  
  15.     if (self)  
  16.     {  
  17.         hortable = [[UITableView alloc]initWithFrame:CGRectMake(22, -22, 276, 320) style:UITableViewStylePlain];//由於使用了仿射變換,所以這裡的frame顯得很詭異,慢慢調吧~
  18.         hortable.delegate = self;  
  19.         hortable.dataSource = self;  
  20.         hortable.layer.borderColor = [UIColor blackColor].CGColor;  
  21.         hortable.layer.borderWidth = 1;  
  22.         hortable.transform = CGAffineTransformMakeRotation(M_PI / 2 *3);  
  23.         hortable.separatorColor = [UIColor redColor];  
  24.         hortable.decelerationRate = UIScrollViewDecelerationRateFast;  
  25.         hortable.showsHorizontalScrollIndicator = NO;  
  26.         hortable.showsVerticalScrollIndicator = NO;  
  27.         [self addSubview:hortable];  
  28.    }  
  29.     return self;  
  30. }  
  31. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  
  32. {  
  33.         return 5;  
  34.  }  
  35. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  36. {  
  37.     NSString *CellIdentifier = [NSString stringWithFormat:@"cell%d",indexPath.row];  
  38.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];  
  39.     if (cell == nil)  
  40.     {  
  41.         cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]autorelease];  
  42.         cell.transform = CGAffineTransformMakeRotation(M_PI/2);  
  43.     }   
  44.        //在這裡新增你的view,就是那些UITableView,<span style="color:#ff0000">注意,關鍵在這裡:如果新增到cell上的table需要下拉重新整理,如果不想滑動時間出現衝突,要保證cell上的UITableView的contentoffset 不等於0和不便宜到最底部,這樣下拉重新整理才沒有問題,例如 當<span style="font-family:Arial,Helvetica,sans-serif">contentoffset.y = 0時候,使其等於1。不然背景的table就會跟著一起滾動,達不到下拉重新整理的效果</span></span>
  45.     return cell;  
  46. }  
  47. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath  
  48. {  
  49.     return 320;  
  50. }  
  51. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath  
  52. {  
  53.     NSLog(@"點選%d",[indexPath row]);  
  54. }  
  55. - (void)setSelected:(BOOL)selected animated:(BOOL)animated  
  56. {  
  57.     [super setSelected:selected animated:animated];  
  58. }  
  59. @end  

這樣就解決了開始的問題了!

最上層的TableView如果下拉重新整理有問題,需要修復偏移量,可以參考如下程式碼:

- (void) correctOffSetForDownPull

{

    if (self.tableView.contentOffset.y ==0) {

self.tableView.contentOffset =CGPointMake(0,1);

    }

if (self.tableView.contentOffset.y == (self.tableView.contentSize.height -self.tableView.frame.size.height)) {

self.tableView.contentOffset =CGPointMake(0, (self.tableView.contentSize.height -self.tableView.frame.size.height) -1);

    }

}

其中橫向UITableView需要pageEnable效果,如下方法可以實現:

-(void)scrollViewWillEndDragging:(UIScrollView*)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inoutCGPoint*)targetContentOffset {

// Variables

    CGPoint offset              = (*targetContentOffset);

    NSIndexPath* indexPath      = [hortableindexPathForRowAtPoint:(*targetContentOffset)];  // Get index path for target row

int numberOfRow = [selftableView:(UITableView *)scrollViewnumberOfRowsInSection:(NSInteger)indexPath.section];

/* Find closest row at *targetContentOffset */

// Row at *targetContentOffset

    CGRect rowRect      = [hortablerectForRowAtIndexPath:indexPath];

// temporary assign

    selectedIndexPath = indexPath;

    CGRect targetRect   = rowRect;

// Next Row

    if (indexPath.row < numberOfRow -1 ){

NSIndexPath *nextPath   = [NSIndexPathindexPathForRow: indexPath.row +1inSection: indexPath.section];

        CGRect nextRowRect      = [hortablerectForRowAtIndexPath: nextPath];

// Compare distance

// if next row is closer, set target rect

        if (fabs(offset.y -CGRectGetMinY(nextRowRect)) <fabs(offset.y -CGRectGetMinY(rowRect))){

            targetRect          = nextRowRect;

            selectedIndexPath   = nextPath;

        }

    }

/* Centering */

    offset = targetRect.origin;

    if (self.centering){

        offset.y -= (hortable.bounds.size.height *0.5 - targetRect.size.height *0.5);

    }

// Assign return value

    (*targetContentOffset) = offset;

// Snap speed

// it seems it's better set it slow when the distance of target offset and current offset is small to avoid abrupt jumps

    float currentOffset = hortable.contentOffset.y;

    float rowH = targetRect.size.height;

    static const float thresholdDistanceCoef  = 0.25;

    if (fabs(currentOffset - (*targetContentOffset).y) > rowH * thresholdDistanceCoef){

hortable.decelerationRate =UIScrollViewDecelerationRateFast;

    } else {

hortable.decelerationRate =UIScrollViewDecelerationRateNormal;

    }

}


到現在為止幾乎接近完美了~