微博主页效果指的是多个包含UIScrollView
(UITableView
/UICollectionView
)的控制器或view在同一个控制器中展示。
- 主控制器包含
UITableView
支持下拉放大,包含PageView
等类似的控件,解决主控制器中UITableView
的滑动和PageView
滑动的冲突; - 多个子控制器都包含
UITableView
或UICollectionView
,解决子控制器中UITableView
/UICollectionView
滑动和主控制器中滑动的冲突。
因为考虑到根据不同的应用,不同的UI,都能使用,写了一个工具类,其实这里面没有什么难懂的地方,就是利用kvo的方式对多个UIScrollView
的状态进行监听。
下面先贴出.h文件的代码:
//
// MultipleScrollViewManager.h
// Join
//
// Created by JOIN iOS on 2018/5/14.
// Copyright © 2018年 huangkejin. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MultipleScrollViewManager : NSObject
/**设置主控制器关联的UIScrollView滑动到多少时,子控制器才允许滑动,
如果不设置或者<=0,则默认主控制器关联的UIScrollView滑动到底部时子控制器开始滑动*/
@property (assign, nonatomic) CGFloat mainOffsetY;
/**销毁 移除观察者*/
- (void)kjMultipleManagerDealloc;
/**
告知子控制器关联的UIScrollView
@param sView UIScrollView
*/
- (void)addChildView:(UIScrollView *)sView ;
/**
告知主控制器关联的UIScrollView
@param sView UIScrollView
*/
- (void)addMainView:(UIScrollView *)sView;
/**
告知主控制器中的分页控制器,解决分页控制器和主控制器的UIScrollView同时滑动的问题
@param sView 分页控制器的UIScrollView
*/
- (void)addMainRelevancyPageView:(UIScrollView *)sView;
// 判断是否显示滚动条,只需要在主控制器设置即可,子控制器不用设置 yes-不显示 NO-显示
@property (assign, nonatomic) BOOL scrollIndicator;
@end
注释都写的很清楚,没有什么需要特别的说明的地方。就是在主控制在dealloc时需要调用一下
/**销毁 移除观察者*/
- (void)kjMultipleManagerDealloc;
下面直接贴出.m文件吧
//
// MultipleScrollViewManager.m
// Join
//
// Created by JOIN iOS on 2018/5/14.
// Copyright © 2018年 huangkejin. All rights reserved.
//
#import "MultipleScrollViewManager.h"
@interface MultipleScrollViewManager ()
/**主view*/
@property (strong, nonatomic) UIScrollView *mainView;
/**主view 滑动状态记录 初始状态是YES,即表示可以滑动*/
@property (assign, nonatomic) BOOL if_mainV_scroll;
/**子view*/
@property (strong, nonatomic) NSMutableArray *childArray;
/**主控制器中的分页控制器*/
@property (strong, nonatomic) UIScrollView *pageView;
@end
@implementation MultipleScrollViewManager
/**销毁 移除观察者*/
- (void)kjMultipleManagerDealloc {
for (UIScrollView *sView in self.childArray) {
[sView removeObserver:self forKeyPath:@"contentOffset"];
}
if (self.mainView) {
[self.mainView removeObserver:self forKeyPath:@"contentOffset"];
[self.mainView removeObserver:self forKeyPath:@"panGestureRecognizer.state"];
}
if (self.pageView) {
[self.pageView removeObserver:self forKeyPath:@"panGestureRecognizer.state"];
}
}
/**
告知子控制器关联的UIScrollView
@param sView UIScrollView
*/
- (void)addChildView:(UIScrollView *)sView {
if (!self.childArray) {
self.childArray = [NSMutableArray arrayWithCapacity:0];
}
NSUInteger index = [self.childArray indexOfObject:sView];
if (index != NSNotFound) {
//移除
[sView removeObserver:self forKeyPath:@"contentOffset"];
[self.childArray removeObject:sView];
}
[self.childArray addObject:sView];
//监听偏移量的变化
[sView addObserver:self
forKeyPath:@"contentOffset"
options:NSKeyValueObservingOptionNew
context:nil];
}
/**
告知关联主控制器的UIScrollView
@param sView UIScrollView
*/
- (void)addMainView:(UIScrollView *)sView {
if (self.mainView) {
//移除观察者
[sView removeObserver:self forKeyPath:@"contentOffset"];
[sView removeObserver:self forKeyPath:@"panGestureRecognizer.state"];
}
self.mainView = sView;
self.if_mainV_scroll = YES;
//监听偏移量的变化
[sView addObserver:self
forKeyPath:@"contentOffset"
options:NSKeyValueObservingOptionNew
context:nil];
//监听手势状态变化
[sView addObserver:self
forKeyPath:@"panGestureRecognizer.state"
options:NSKeyValueObservingOptionNew
context:nil];
}
/**
告知主控制器中的分页控制器,解决分页控制器和主控制器的UIScrollView同时滑动的问题
@param sView 分页控制器的UIScrollView
*/
- (void)addMainRelevancyPageView:(UIScrollView *)sView {
if (sView) {
if (self.pageView) {
//防止重复传入,重复监听,当有时,则删除监察者
[self.pageView removeObserver:self forKeyPath:@"panGestureRecognizer.state"];
}
self.pageView = sView;
//监听手势状态变化
[sView addObserver:self
forKeyPath:@"panGestureRecognizer.state"
options:NSKeyValueObservingOptionNew
context:nil];
}
}
/**观察者事件*/
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
if (object == self.pageView) {//主控制器中关联的分页管理器
UIScrollView *sView = object;
if (sView.panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
self.mainView.scrollEnabled = NO;
} else {
self.mainView.scrollEnabled = YES;
}
} else if (object == self.mainView) {//主控制器中的UIScrollView
if ([keyPath isEqualToString:@"panGestureRecognizer.state"]) {
if (self.pageView && [change[NSKeyValueChangeNewKey] integerValue] == UIGestureRecognizerStateChanged) {
self.pageView.scrollEnabled = NO;
} else {
self.pageView.scrollEnabled = YES;
}
}
else if ([keyPath isEqualToString:@"contentOffset"]) {
CGFloat vHeight = self.mainView.frame.size.height;
if (self.if_mainV_scroll) {
//获取主控制器中sView的偏移量
CGPoint point = [change[NSKeyValueChangeNewKey] CGPointValue];
//判断是否滑动到允许子控制器滑动的位置
BOOL isBottom = self.mainView.contentSize.height - point.y <= vHeight;
if (self.mainOffsetY > 0) {//为了支持由外部设置偏移量决定子控制器何时允许滑动
isBottom = point.y >= self.mainOffsetY;
}
if (isBottom) {
self.if_mainV_scroll = NO;
self.mainView.showsVerticalScrollIndicator = NO;
//滑动到指定的位置,让自己不能滑动,并让子控制器可以滑动
CGFloat offsetY = self.mainOffsetY > 0 ? self.mainOffsetY : self.mainView.contentSize.height - vHeight;
[self.mainView setContentOffset:CGPointMake(0, offsetY)];
for (UIScrollView *tmpView in self.childArray) {
tmpView.showsVerticalScrollIndicator = !self.scrollIndicator;
}
}
} else {
//主控制器不允许滑动
CGFloat offsetY = self.mainOffsetY > 0 ? self.mainOffsetY : self.mainView.contentSize.height - vHeight;
if (self.mainView.contentOffset.y != offsetY) {
[self.mainView setContentOffset:CGPointMake(0, offsetY) animated:NO];
}
}
}
} else {//子控制器中的UIScrollView
UIScrollView *sView = object;
if ([keyPath isEqualToString:@"contentOffset"]) {
if (self.if_mainV_scroll) {
//当主控制器可以滑动的时候,子控制器不允许滑动
if (sView.contentOffset.y != 0) {
[sView setContentOffset:CGPointZero];
}
} else {
//子控制器的sView偏移量有变化
CGPoint point = [change[NSKeyValueChangeNewKey] CGPointValue];
//decelerating用于判断手指是否离开屏幕,YES表示手指已离开屏幕,这里当手指离开屏幕时if_mainV_scroll状态不变化
if (point.y < 0 && !sView.decelerating) {
//当子控制器滑动到顶部后 ,让主控制器可以滑动
self.if_mainV_scroll = YES;
self.mainView.showsVerticalScrollIndicator = !self.scrollIndicator;
for (UIScrollView *tmpView in self.childArray) {
//当主控制器可以滑动时,子控制器自动回到顶部
if (tmpView.contentOffset.y != 0) {
[tmpView setContentOffset:CGPointZero];
}
tmpView.showsVerticalScrollIndicator = NO;
}
}
}
}
}
}
@end
注释都写的很清楚,因为没什么难懂的地方,就不多做代码解释了,下面说说怎么使用:
1. 主控制器
- 需要申明管理器属性
/**多页面滑动管理器*/
@property (strong, nonatomic) MultipleScrollViewManager *sManager;
- 初始化
self.sManager = [MultipleScrollViewManager new];
- 把相关的View交给管理器
//添加主控器到管理器中
[self.sManager addMainView:self.tableView];
//把pageview交给管理者
[self.sManager addMainRelevancyPageView:self.bottomSView];
- 当然还要把管理者传给子控制器,让子控制器把相关的view交给管理者
//闲聊
G_TalkViewController *talk = [KAppDelegate.relationStoryboard instantiateViewControllerWithIdentifier:@"G_TalkViewController"];
talk.sManager = self.sManager;
[self addChildViewController:talk];
//晒单
G_FeedViewController *feed = [[G_FeedViewController alloc] init];
feed.sManager = self.sManager;
[self addChildViewController:feed];
//组局
G_InviteViewController *invite = [KAppDelegate.relationStoryboard instantiateViewControllerWithIdentifier:@"G_InviteViewController"];
invite.sManager = self.sManager;
[self addChildViewController:invite];
2. 子控制器
- 就只需要把
UIScrollView
交给管理者即可
//交给管理者进行统一管理
if (self.sManager) {
[self.sManager addChildView:self.collectionView];
}
由于代码就贴在文章中了,所以就不用上传github了,复制过去就直接可以使用,写这个也是为了重新梳理一遍,想想怎么改进的更好或者有更好的方案,有什么问题直接在下面问我。
最终还是上传到githubKJScrollViewManager上了,希望能帮到大家
同时也支持pod导入到项目使用
pod 'KJMultipleScrollViewManager'