封装一个滑动切换页面的框架

项目中经常会用到这样的页面结构,页面顶部有tab栏,点击能切换到对应的页面(有滑动效果),下方的页面也能够拖动:


lADPDgQ9qbtgDdLNBQDNAtA_720_1280.jpg_620x10000q90g.jpg

当页面变多时,tab栏也能够拖动了,并且当页面滑动使tab栏的头部或尾部tab选中的时候,tab栏还会根据情况自动滑动:


IMB_N5geVC.GIF

还有可能有其他的效果,比如选中的tab下方有下划线,下划线的长度在页面滑动过程中会根据两个tab的长度实时的变化,还有tab的文字颜色也会根据页面的滑动变化,等等。
因此,封装了一个滑动切换页面的框架,能够应对这种页面结构的大多数需求。最简单的使用如下:
    // 创建tab栏对应的title数组
    NSArray<NSString *> *titleArr = @[@"腾讯", @"蚂蚁金服", @"YY", @"网易"];
    // 创建页面对应的子控制器数组
    NSMutableArray<UIViewController *> *childVcArr = [NSMutableArray new];
    for (int i = 0; i < titleArr.count; i++) {
        UIViewController *vc = [UIViewController new];
        vc.view.backgroundColor = [UIColor whiteColor];
        UILabel *label = [UILabel lzs_labelWithText:titleArr[i] font:[UIFont systemFontOfSize:14] color:[UIColor redColor]];
        label.center = vc.view.center;
        [vc.view addSubview:label];
        [childVcArr addObject:vc];
    }
    // tab栏的样式
    LZSTitleStyle *style = [LZSTitleStyle new];
    // 创建pageView
    LZSPageView *pageView = [[LZSPageView alloc] initWithFrame:self.view.bounds titleArr:titleArr style:style childViewControllers:childVcArr parentViewController:self];
    [self.view addSubview:pageView];

页面如下:


image.png

LZSPageView负责tab栏和页面的交互,以及tab栏的样式。所以需要四个参数构建pageView:
1.tab栏对应的title数组
2.页面对应的子控制器数组
3.负责tab栏样式的对象( LZSTitleStyle)
4.页面对应的子控制器数组的父控制器
这样就得到了一个pageView。具体的页面的业务逻辑,还是写在页面对应的子控制器的类中。
其中LZSTitleStyle这个类,是用来自定义tab栏样式的,如果你想使用框架的默认样式,那么创建一个LZSTitleStyle对象然后传进来就行了,如果想自定义样式,LZSTitleStyle提供如下的属性:

@interface LZSTitleStyle : NSObject

//titleView高度,默认44
@property(nonatomic,assign)float titleViewHeight;
//titleView的宽度,默认等于pageView宽度
@property(nonatomic,assign)float titleViewWidth;
//titleView的x值,当titleView的宽度等于pageView宽度时,一定为0,当titleView的宽度不能等于pageView宽度时,可以设置,不设置默认为0
@property(nonatomic,assign)float titleViewX;
//title未选中颜色,默认黑色
@property(nonatomic,strong)UIColor *normalColor;
//title选中颜色,默认蓝色
@property(nonatomic,strong)UIColor *selectColor;
//字体大小
@property(nonatomic,assign)float fontSize;
//标题栏不能滚动时titleLab宽度等于pageView宽度/title个数,可以滚动时titleLab宽度等于文字宽度,默认不能滚动
@property(nonatomic,assign)BOOL isScrollEnable;
//titleLab间距,标题栏不能滚动时一定为0,能滚动时可以设置间距,默认30,最左边距和最右边距为itemMargin/2
@property(nonatomic,assign)float itemMargin;
//是否显示下划线,默认显示
@property(nonatomic,assign)BOOL isShowScrollLine;
//标题栏可以滚动时下划线宽度一定会等于文字的宽度,不能滚动时可以设置下划线宽度,默认等于titleLab的宽度
@property(nonatomic,assign)float scrollLineWidth;
//下划线高度,默认2
@property(nonatomic,assign)float scrollLineHeight;
//下划线颜色,默认蓝色
@property(nonatomic,strong)UIColor *scrollLineColor;

@end

以上的LZSPageView的使用是将tab栏和子页面当做一个整体来看的。但是有时候,tab栏和子页面可能并不适合当做一个整体,就像刚开始这张图:


lADPDgQ9qbtgDdLNBQDNAtA_720_1280.jpg_620x10000q90g.jpg

实际上,tab栏是布局在导航栏上面的,像这样子

self.navigationItem.titleView = titleView;

所以这个时候就不能直接创建出pageView了。LZSPageView也提供更加灵活的创建方式来应对这种tab栏和子页面分开的情况:

    // 创建LZSTitleStyle,设置属性
    LZSTitleStyle *style = [LZSTitleStyle new];
    style.titleViewHeight = 44;
    style.titleViewWidth = 240;
    style.titleViewX = 0;
    style.normalColor = k3A3D48;
    style.selectColor = k4C72F5;
    style.fontSize = 15;
    style.scrollLineWidth = 30;
    style.scrollLineHeight = 2.5;
    style.scrollLineColor = k4C72F5;
    // 创建LZSTitleView
    self.titleView = [[LZSTitleView alloc] initWithFrame:CGRectMake(0, 0, style.titleViewWidth, style.titleViewHeight) titleArr:@[@"成长故事",@"企业文化",@"表彰文化"] style:style];
    self.navigationItem.titleView = self.titleView;
    // 创建LZSContentView
    self.childVcArr = @[[self setupGrowthCourseVc],[self setupEnterpriseCultureVc],[self setupPraiseCultureVc]];
    self.contentView = [[LZSContentView alloc] initWithFrame:CGRectMake(0, 0, self.view.width, self.view.height-kStatusBarHeight-self.navigationController.navigationBar.height) childViewControllers:self.childVcArr parentViewController:self];
    [self.view addSubview:self.contentView];
    // 关键,互相设置代理
    self.titleView.delegate = self.contentView;
    [self.contentView addDelegate:self.titleView];

就是分别创建包含tab栏的LZSTitleView和包含子页面的LZSContentView,然后两个view互相成为代理。这样,你就可以单独拿到titleView和contentView,而titleView和contentView之间的交互逻辑还是通过框架来解决。

这里LZSContentView设置代理的方式是使用addDelegate这个方法,而不是提供一个delegate属性去设置。因为可能还有其他地方需要监听LZSContentView的滚动,提供一个delegate属性就只能给titleView使用了,其他地方就监听不了LZSContentView的滚动。而addDelegate的方法可以让多个类成为LZSContentView的代理。内部实现是使用一个数组去记录这些delegate,然后需要调用的时候遍历数组调用delegate的方法,这里使用的这个数组不能使NSMutableArray,因为NSMutableArray会对它的元素强引用,OC提供了NSPointerArray这个类,这个类对它的元素是弱引用。

这个框架还对子控制器的view的加载时机(viewDidLoad这个方法)做了处理。LZSContentView的内部使用一个UICollectionView去放置子控制器的view的,不过子控制器的view并不会在cell一出现在界面的时候就加载,而会等到collectionView停止滚动的时候再加载。这样做的目的是,如果cell一出现在界面就加载子控制器的view(也就是走viewDidLoad方法),如果子页面很多,而我是通过点击tab栏去切换子页面的,那么刚开始我直接点击tab栏很后面的tab时,就会滑过中间很多界面,这些界面可能是用户不想看的,然后这些页面就都走了viewDidLoad方法,这样既会造成滑动动画的卡顿,也会造成不必要的页面的加载:


IMB_NIDknd.GIF

相关代码如下:

@interface LZSContentView ()<UICollectionViewDataSource,UICollectionViewDelegate>

@property(nonatomic,strong)NSMutableArray<NSNumber *> *isLoadViewArr;

@end

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    LZSContentCollectionViewCell *cell = [_collectionView dequeueReusableCellWithReuseIdentifier:@"contentCollectionViewCell" forIndexPath:indexPath];
    if (indexPath.row == 0) {
        cell.vc = _childVcArr[indexPath.row];
        _isLoadViewArr[0] = @1;
    } else {
        NSNumber *number = _isLoadViewArr[indexPath.row];
        if (number.integerValue) {
            cell.vc = _childVcArr[indexPath.row];
        } else {
            cell.vc = nil;
        }
    }
    
    
    return cell;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

    NSNumber *number = _isLoadViewArr[indexPath.row];
    if (!number.integerValue) {
        cell.vc = _childVcArr[indexPath.row];
        _isLoadViewArr[indexPath.row] = @1;
    }
 
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
    
    NSNumber *number = _isLoadViewArr[indexPath.row];
    if (!number.integerValue) {
        cell.vc = _childVcArr[indexPath.row];
        _isLoadViewArr[indexPath.row] = @1;
    }
}

就是使用一个数组记录子控制的view是否已经加载过了,如果没有加载过,那么在- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath里设置 cell.vc = nil。如果已经加载过了,则设置cell.vc = _childVcArr[indexPath.row]。在- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView和- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView里判断当前页面子控制的view是否已经加载过了,如果没有加载,设置 cell.vc = _childVcArr[indexPath.row]。

github地址:https://github.com/linzhesheng/LZSPageView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268