柚子快報(bào)激活碼778899分享:iOS 吸頂效果
柚子快報(bào)激活碼778899分享:iOS 吸頂效果
項(xiàng)目中,在列表向上滾動(dòng)時(shí),有時(shí)需要將某個(gè)控件置頂,這就是我們常見(jiàn)的吸頂效果。
1. UITableView 吸頂效果
UITableView是自帶吸頂效果,我們把需要置頂?shù)目丶O(shè)置為SectionHeaderView,這樣在滾動(dòng)時(shí),該控件會(huì)自動(dòng)置頂。
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UKNestedTableView alloc] init];
_tableView.bounces = NO;
_tableView.showsVerticalScrollIndicator = NO;
_tableView.delegate = self;
_tableView.dataSource = self;
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"CellId"];
}
return _tableView;
}
#pragma mark - UITableViewDataSource -
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return 1;
}
return 20;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
return 150;
}
return 60;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section == 1) {
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 50)];
headerView.backgroundColor = [UIColor blueColor];
return headerView;
}
return nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if (section == 1) {
return 50;
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellId" forIndexPath:indexPath];
if (indexPath.section == 0) {
cell.backgroundColor = [UIColor yellowColor];
cell.textLabel.text = @"section 0";
} else {
if (indexPath.row % 2 == 0) {
cell.backgroundColor = [UIColor grayColor];
} else {
cell.backgroundColor = [UIColor whiteColor];
}
cell.textLabel.text = [NSString stringWithFormat:@"item - %ld", indexPath.row];
}
return cell;
}
自定義UKNestedTableView
@implementation UKNestedTableView
- (instancetype)init {
self = [super initWithFrame:CGRectZero style:UITableViewStylePlain];
if (self) {
self.backgroundColor = [UIColor whiteColor];
self.separatorColor = [UIColor clearColor];
self.separatorStyle = UITableViewCellSeparatorStyleNone;
if (@available(iOS 11.0, *)) {
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
self.estimatedRowHeight = 0.000;
self.estimatedSectionHeaderHeight = 0.000;
self.estimatedSectionFooterHeight = 0.000;
if (@available(iOS 13.0,*)) {
self.automaticallyAdjustsScrollIndicatorInsets = NO;
}
if (@available(iOS 15.0,*)) { // 去除表格頭留白
self.sectionHeaderTopPadding = YES;
}
}
return self;
}
@end
效果如下
2. 帶TabView的吸頂效果
UITableView的吸頂效果能滿足部分的要求,但在實(shí)際應(yīng)用中,需要置頂?shù)耐且恍?biāo)簽頁(yè),對(duì)應(yīng)的也是多個(gè)列表。
我們用UKTabView作為置頂?shù)目丶?,并?duì)應(yīng)多個(gè)內(nèi)容。
- (UKTabView *)tabView {
if (!_tabView) {
_tabView = [[UKTabView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 50)];
[_tabView setIndicatorWidth:80 height:2 radius:1 color:[UIColor blueColor]];
UKCustomTabItemView *tabItemView1 = [[UKCustomTabItemView alloc] init];
[tabItemView1 setText:@"選項(xiàng)1"];
[_tabView addItemView:tabItemView1];
UKCustomTabItemView *tabItemView2 = [[UKCustomTabItemView alloc] init];
[tabItemView2 setText:@"選項(xiàng)2"];
[_tabView addItemView:tabItemView2];
_tabView.delegate = self;
[_tabView setSelection:0];
}
return _tabView;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section == 1) {
return self.tabView;
}
return nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellId" forIndexPath:indexPath];
if (indexPath.section == 0) {
cell.backgroundColor = [UIColor yellowColor];
cell.textLabel.text = @"section 0";
} else {
if (indexPath.row % 2 == 0) {
if (self.selection == 0) {
cell.backgroundColor = [UIColor grayColor];
} else {
cell.backgroundColor = [UIColor darkGrayColor];
}
} else {
cell.backgroundColor = [UIColor whiteColor];
}
cell.textLabel.text = [NSString stringWithFormat:@"item %ld - %ld", self.selection, indexPath.row];
}
return cell;
}
#pragma mark - UKTabViewDelegate -
- (void)onTabViewSelected:(UKTabView *)tabView position:(NSInteger)position {
self.selection = position;
[self.tableView reloadData];
}
效果如下
上述的方法簡(jiǎn)單地實(shí)現(xiàn)了標(biāo)簽頁(yè)置頂和選項(xiàng)卡切換功能,但由于我們只能共用一個(gè)列表,所以會(huì)發(fā)生兩個(gè)標(biāo)簽頁(yè)都滾動(dòng)的現(xiàn)象。
為此,我們需要優(yōu)化滾動(dòng)的偏移,首先在滾動(dòng)結(jié)束時(shí)記錄偏移量,然后在切換標(biāo)簽頁(yè)時(shí)設(shè)置原有的偏移量。
@property(nonatomic, assign) NSInteger selection;
@property(nonatomic, assign) CGFloat tab1Offset;
@property(nonatomic, assign) CGFloat tab2Offset;
// 拖動(dòng)結(jié)束
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
NSLog(@"scrollViewDidEndDragging");
[self recordOffset:scrollView];
}
// 滾動(dòng)結(jié)束
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(@"scrollViewDidEndDecelerating");
[self recordOffset:scrollView];
}
- (void)recordOffset:(UIScrollView *)scrollView {
if (self.selection == 0) {
self.tab1Offset = scrollView.contentOffset.y;
NSLog(@"tab1Offset = %.2f", self.tab1Offset);
} else if (self.selection == 1) {
self.tab2Offset = scrollView.contentOffset.y;
NSLog(@"tab2Offset = %.2f", self.tab2Offset);
}
}
在切換標(biāo)簽頁(yè)時(shí),設(shè)置實(shí)際的偏移量
- (void)onTabViewSelected:(UKTabView *)tabView position:(NSInteger)position {
self.selection = position;
[self.tableView reloadData];
// 有時(shí)設(shè)置tableView.contentOffset無(wú)效,需要提前刷新
[self.tableView layoutIfNeeded];
if (position == 0) {
self.tableView.contentOffset = CGPointMake(0, self.tab1Offset);
} else if (position == 1) {
self.tableView.contentOffset = CGPointMake(0, self.tab2Offset);
}
}
效果如下
雖然我們記錄了原有的偏移量,但從實(shí)際的效果來(lái)看,切換時(shí)TabView會(huì)在同樣的位置,閃爍比較嚴(yán)重。為此,我們需要盡量保持TabView的位置。
- (void)onTabViewSelected:(UKTabView *)tabView position:(NSInteger)position {
self.selection = position;
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
if (position == 0) {
self.tab1Offset = [self getDestOffset:self.tab1Offset originOffset:self.tab2Offset];
self.tableView.contentOffset = CGPointMake(0, self.tab1Offset);
} else if (position == 1) {
self.tab2Offset = [self getDestOffset:self.tab2Offset originOffset:self.tab1Offset];
self.tableView.contentOffset = CGPointMake(0, self.tab2Offset);
}
}
// 如果TabView已經(jīng)置頂,切換時(shí)保持置頂。
// 1、如果切換后的內(nèi)容已經(jīng)置頂,保持原有效果
// 2、如果切換后的內(nèi)容沒(méi)有置頂,修改切換后的內(nèi)容為置頂
// 如果TabView沒(méi)有制度,切換后保持一致
- (CGFloat)getDestOffset:(CGFloat)destOffset originOffset:(CGFloat)originOffset {
if (originOffset >= 150) {
if (destOffset >= 150) {
return destOffset;
} else {
return 150;
}
} else {
return originOffset;
}
}
效果如下
雖然現(xiàn)在的方案已經(jīng)解決了大部分的需求,但還是留下了一點(diǎn)瑕疵,
內(nèi)容只能用UIScrollView顯示為了保持UKTableView保持位置不變,不能完全保證內(nèi)容的偏移位置。如果一個(gè)內(nèi)容較短的情況下,依然會(huì)有偏移量的問(wèn)題,雖然我們可以通過(guò)填充空白內(nèi)容來(lái)改善這個(gè)問(wèn)題,但又增加了很多工作量。內(nèi)容切換時(shí)沒(méi)有平順的效果。
3. UITableView+UICollectionView嵌套
為了盡可能的完善我們的吸頂效果,我們嘗試用UITableView+UICollectionView的組合來(lái)實(shí)現(xiàn)吸頂和左右滑動(dòng)二種效果。
我們自定義UKNestedScrollView
@interface UKNestedScrollView()
@property(nonatomic, strong) NSMutableArray
@property(nonatomic, assign) BOOL dragging;
@end
@implementation UKNestedScrollView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupInitialUI];
}
return self;
}
// 設(shè)置表頭
- (void)setHeaderView:(UIView *)headerView {
self.tableView.tableHeaderView = headerView;
self.headerHeight = headerView.frame.size.height;
}
// 添加標(biāo)簽頁(yè)和內(nèi)容
- (void)addTabView:(UKTabItemView *)itemView contentView:(UITableView *)contentView {
[self.tabView addItemView:itemView];
[self.contentViewArray addObject:contentView];
[self.collectionView reloadData];
}
- (void)setupInitialUI {
// UKNestedScrollView包含一個(gè)UITableView
[self addSubview:self.tableView];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.bottom.equalTo(self);
}];
}
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UKNestedTableView alloc] init];
_tableView.bounces = NO;
_tableView.showsVerticalScrollIndicator = NO;
_tableView.delegate = self;
_tableView.dataSource = self;
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"CellId"];
}
return _tableView;
}
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UKNestedTableView alloc] init];
_tableView.bounces = NO;
_tableView.showsVerticalScrollIndicator = NO;
_tableView.delegate = self;
_tableView.dataSource = self;
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"CellId"];
}
return _tableView;
}
// SectionHeaderView包含UKTabView和UICollectionView
- (UIView *)sectionHeaderView {
if (!_sectionHeaderView) {
_sectionHeaderView = [[UIView alloc] initWithFrame:self.frame];
[_sectionHeaderView addSubview:self.tabView];
[_sectionHeaderView addSubview:self.collectionView];
}
return _sectionHeaderView;
}
- (UKTabView *)tabView {
if (!_tabView) {
_tabView = [[UKTabView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 50)];
[_tabView setIndicatorWidth:80 height:2 radius:1 color:[UIColor blueColor]];
_tabView.delegate = self;
}
return _tabView;
}
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.itemSize = CGSizeMake(self.frame.size.width, self.frame.size.height - 50);
layout.minimumLineSpacing = CGFLOAT_MIN;
layout.minimumInteritemSpacing = CGFLOAT_MIN;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 50, self.frame.size.width, self.frame.size.height - 50) collectionViewLayout:layout];
_collectionView.pagingEnabled = YES;
_collectionView.bounces = NO;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.dataSource = self;
_collectionView.delegate = self;
[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CellId"];
}
return _collectionView;
}
#pragma mark - UITableViewDataSource -
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return self.frame.size.height;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return self.sectionHeaderView;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [[UITableViewCell alloc] init];
}
#pragma mark - UICollectionViewDataSource -
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.contentViewArray.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CellId" forIndexPath:indexPath];
UITableView *contentView = self.contentViewArray[indexPath.row];
[contentView removeFromSuperview];
[cell.contentView addSubview:contentView];
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.bottom.equalTo(cell.contentView);
}];
return cell;
}
#pragma mark - UIScrollViewDelegate -
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if (scrollView == self.collectionView) {
self.dragging = YES;
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.collectionView) {
if (self.dragging) {
CGFloat width = scrollView.contentOffset.x;
NSInteger page = width/self.frame.size.width + 0.5;
[self.tabView setSelection:page offsetRatio:(width/self.frame.size.width - page)];
}
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView == self.collectionView) {
CGFloat width = scrollView.contentOffset.x;
NSInteger page = width/self.frame.size.width + 0.5;
[self.tabView setSelection:page];
self.dragging = NO;
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (scrollView == self.collectionView && !decelerate) {
CGFloat width = scrollView.contentOffset.x;
NSInteger page = width/self.frame.size.width + 0.5;
[self.tabView setSelection:page];
self.dragging = NO;
}
}
#pragma mark - UKTabViewDelegate -
- (void)onTabViewSelected:(UKTabView *)tabView position:(NSInteger)position {
[self collectionViewScrollToPosition:position];
}
為了讓UICollectionView內(nèi)的手勢(shì)能被UITableView接收,需要在UKNestedTableView里面加上
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
顯示如下
我們可以看到當(dāng)列表滑動(dòng)時(shí),兩個(gè)列表都在滑動(dòng),而且里面的內(nèi)容的滑動(dòng)更快。這主要是因?yàn)槔鈨蓚€(gè)列表都在滑動(dòng),所以里面的列表其實(shí)是兩個(gè)滑動(dòng)距離相加,所有我們需要在外面列表滑動(dòng)時(shí),禁止里面列表的滑動(dòng)。
if (scrollView == self.tableView) {
self.offset = self.tableView.contentOffset.y;
// changed表示外面列表在滑動(dòng)
self.changed = YES;
} else {
NSInteger position = 0;
for (UIScrollView *contentView in self.contentViewArray) {
if (contentView == scrollView) {
// 如果外面列表滑動(dòng),禁止里面列表滑動(dòng)事件
if (self.changed) {
scrollView.contentOffset = CGPointMake(0, [self.offsetArray[position] floatValue]);
self.changed = NO;
} else {
// 記錄當(dāng)前頁(yè)面偏移量,方便后面禁止事件
self.offsetArray[position] = [NSNumber numberWithFloat:scrollView.contentOffset.y];
}
break;
}
position++;
}
}
效果如下
現(xiàn)在的效果已經(jīng)基本滿足了我們的需求,有吸頂效果、能左右滑動(dòng)、能記錄列表偏移量,內(nèi)容滑動(dòng)時(shí)也比較平順了。
最后我們嘗試了一下下拉時(shí)控制內(nèi)容先下拉,也許后面有用
if (scrollView == self.tableView) {
self.originOffset = self.offset;
self.offset = self.tableView.contentOffset.y;
self.changed = YES;
} else {
NSInteger position = 0;
for (UIScrollView *contentView in self.contentViewArray) {
if (contentView == scrollView) {
CGFloat scrollViewOffset = scrollView.contentOffset.y - [self.offsetArray[position] floatValue];
if (scrollViewOffset > 0) {
if (self.changed) {
scrollView.contentOffset = CGPointMake(0, [self.offsetArray[position] floatValue]);
self.changed = NO;
} else {
self.offsetArray[position] = [NSNumber numberWithFloat:scrollView.contentOffset.y];
}
} else if (scrollViewOffset < 0) {
if (self.changed) {
self.offset = self.originOffset;
self.tableView.delegate = nil;
self.tableView.contentOffset = CGPointMake(0, self.offset);
self.tableView.delegate = self;
self.changed = NO;
}
self.offsetArray[position] = [NSNumber numberWithFloat:scrollView.contentOffset.y];
}
break;
}
position++;
}
}
柚子快報(bào)激活碼778899分享:iOS 吸頂效果
好文推薦
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。