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?- //實現Table
- CGRect scrollRect = CGRectMake(0, 0, 320, 460);
- self.tableBgScroll = [[[UITableView alloc] initWithFrame:scrollRect style:UITableViewStylePlain] autorelease];
- [self.tableBgScroll setDelegate:self];
-
[self.tableBgScroll setDataSource:self];
- //Table的資料來源方法
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return 1;
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *cellname = @"cell";
- InfoCell *cell = (InfoCell *)[tableView dequeueReusableCellWithIdentifier:cellname];
- if (cell == nil)
- {
- cell = [[[InfoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellname]autorelease];
- }
- cell.selectionStyle = UITableViewCellSelectionStyleNone;
- return cell;
- }
- - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- return 460;
- }
InfoCell實現:
[cpp] view plaincopyprint?- #import <UIKit/UIKit.h>
- @interface InfoCell : UITableViewCell<UITableViewDataSource, UITableViewDelegate>
- {
- UITableView *hortable;
- }
- @end
- @implementation InfoCell
- - (void)dealloc
- {
- [super dealloc];
- }
- - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
- {
- self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
- if (self)
- {
- hortable = [[UITableView alloc]initWithFrame:CGRectMake(22, -22, 276, 320) style:UITableViewStylePlain];//由於使用了仿射變換,所以這裡的frame顯得很詭異,慢慢調吧~
- hortable.delegate = self;
- hortable.dataSource = self;
- hortable.layer.borderColor = [UIColor blackColor].CGColor;
- hortable.layer.borderWidth = 1;
- hortable.transform = CGAffineTransformMakeRotation(M_PI / 2 *3);
- hortable.separatorColor = [UIColor redColor];
- hortable.decelerationRate = UIScrollViewDecelerationRateFast;
- hortable.showsHorizontalScrollIndicator = NO;
- hortable.showsVerticalScrollIndicator = NO;
- [self addSubview:hortable];
- }
- return self;
- }
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return 5;
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSString *CellIdentifier = [NSString stringWithFormat:@"cell%d",indexPath.row];
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil)
- {
- cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]autorelease];
- cell.transform = CGAffineTransformMakeRotation(M_PI/2);
- }
- //在這裡新增你的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>
- return cell;
- }
- - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- return 320;
- }
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSLog(@"點選%d",[indexPath row]);
- }
- - (void)setSelected:(BOOL)selected animated:(BOOL)animated
- {
- [super setSelected:selected animated:animated];
- }
- @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;
}
}
到現在為止幾乎接近完美了~