美文网首页iOS技术点iOS开发好文实用工具
OC版写一个快速集成网易新闻,腾讯视频,头条首页的ZJScrol

OC版写一个快速集成网易新闻,腾讯视频,头条首页的ZJScrol

作者: ZeroJ | 来源:发表于2016-05-07 21:05 被阅读6939次

最终效果

oc版滚动示例.gif
oc版滚动示例2.gif
oc版滚动示例3.gif
oc版滚动示例4.gif
oc版滚动示例5.gif
oc版滚动示例6.gif
oc版滚动示例7.gif
oc版滚动示例8.gif 图片在左边
图片在右边.gif
图片在上面.gif
只显示图片.gif

一.构思部分:

打算分为三个部分, 滑块部分View, 内容显示部分View, 包含滑块View和显示内容View的View,以便于可以灵活的使用

1. 滑块部分View

1.1 要实现滑块可以滚动, 考虑可以直接使用collectionView, 但是这里还是直接使用scrollView方便里面的控件布局

1.2 要实现滑块的点击, 可以直接使用UIButton, 但是经过尝试, 要让button的frame随着文字的宽度来自适应实现比较麻烦, 所以选择了使用UILabel添加点击手势来实现点击事件,这里使用了closures来实现(可以使用代理模式)

1.3 实现对应的滚动条和遮盖同步移动的效果,文字颜色渐变功能(在点击的时候直接使用一个动画就可以简单的完成了)

2. 内容显示部分View

2.1 用来作为包含子控制器的view的容器, 并且实现可以分页滚动的效果

2.2 要实现分页滚动, 可以使用UIScrollView来实现, 但是这样就要考虑UIScrollView上的各个view的复用的问题, 其中细节还是很麻烦, 所以直接使用了UICollectionView(利用他的重用机制)来实现

2.3 将每一个子控制器的view直接添加到对应的每一个cell的contentView中来展示, 所以这里需要注意cell重用可能带来的内容显示不正常的问题, 这里采用了每次添加contentView的内容时移除所有的subviews(也可以直接给每个cell用不同的reuseIdentifier实现)

2.4 实现实时监控滚动的进度提供给滑块部分来同步调整滚动条和遮盖,文字颜色的渐变, 并且在每次滚动完成的时候可以通知给滑块来调整他的内容

3. 包含滑块View和显示内容View的View

3.1 因为滑块部分View和内容显示部分View是相对独立的部分, 在这里只需要实现两者的通信即可

3.2 可以自定义滑块部分View和内容显示部分View的frame

a. 滑块部分

1. 基本属性

// 滚动条
@property (weak, nonatomic) UIView *scrollLine;
// 遮盖
@property (weak, nonatomic) UIView *coverLayer;
// 滚动scrollView
@property (weak, nonatomic) UIScrollView *scrollView;
// 背景ImageView
@property (weak, nonatomic) UIImageView *backgroundImageView;
// 附加的按钮
@property (weak, nonatomic) UIButton *extraBtn;
/// 所有的title设置 -> 使用了一个class ZJSegmentStyle, 将可以自定义的部分全部暴露了出来, 使用的时候就可以比较方便的自定义很多属性  -> 初始化时传入
@property (strong, nonatomic) ZJSegmentStyle *segmentStyle;
// 所有的标题
@property (strong, nonatomic) NSArray *titles;
// 用于懒加载计算文字的rgb差值, 用于颜色渐变的时候设置
@property (strong, nonatomic) NSArray *deltaRGB;
@property (strong, nonatomic) NSArray *selectedColorRgb;
@property (strong, nonatomic) NSArray *normalColorRgb;
/** 缓存所有标题label */
@property (nonatomic, strong) NSMutableArray *titleLabels;
// 缓存计算出来的每个标题的宽度
@property (nonatomic, strong) NSMutableArray *titleWidths;
// 响应标题点击
@property (copy, nonatomic) TitleBtnOnClickBlock titleBtnOnClick;


逻辑处理

#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect )frame segmentStyle:(ZJSegmentStyle *)segmentStyle titles:(NSArray *)titles titleDidClick:(TitleBtnOnClickBlock)titleDidClick {
    if (self = [super initWithFrame:frame]) {
        self.segmentStyle = segmentStyle;
        self.titles = titles;
        self.titleBtnOnClick = titleDidClick;
        _currentIndex = 0;
        _oldIndex = 0;
        _currentWidth = frame.size.width;
        
        if (!self.segmentStyle.isScrollTitle) { // 不能滚动的时候就不要把缩放和遮盖或者滚动条同时使用, 否则显示效果不好
            
            self.segmentStyle.scaleTitle = !(self.segmentStyle.isShowCover || self.segmentStyle.isShowLine);
        }
        
        // 这个函数里面设置了基本属性中的titles, labelsArray, titlesWidthArray,并且添加了label到scrollView上
        [self setupTitles];
        // 这个函数里面设置了遮盖, 滚动条,和label的初始化位置
        [self setupUI];

    }
    
    return self;
}

#pragma mark - button action
  -> 处理点击title的时候实现标题的切换,和遮盖,滚动条...的位置调整, 同时执行了响应点击的block, 以便于外部相应点击方法
- (void)titleLabelOnClick:(UITapGestureRecognizer *)tapGes {
    
    ZJCustomLabel *currentLabel = (ZJCustomLabel *)tapGes.view;
    
    if (!currentLabel) {
        return;
    }
    
    _currentIndex = currentLabel.tag;
    // 同步更改UI
    [self adjustUIWhenBtnOnClickWithAnimate:true];
}

- (void)adjustTitleOffSetToCurrentIndex:(NSInteger)currentIndex  -> 更改scrollview的contentOffSet来居中显示title


     // 手动滚动时需要提供动画效果
- (void)adjustUIWithProgress:(CGFloat)progress oldIndex:(NSInteger)oldIndex currentIndex:(NSInteger)currentIndex -> 提供给外部来执行标题切换之间的动画效果(注意这个方法里面进行了一些简单的数学计算以便于"同步" 滚动滚动条和cell )
    这里以滑块的位置x变化为例, 其他类似
    
    CGFloat xDistance = currentLabel.zj_x - oldLabel.zj_x;
    这个xDistance就是滑块将要从一个label下面移动到下一个label下面所需要移动的路程,
    
    这个progress是外界提供来的, 表示当前已经移动的百分比(0 --- 1)是多少了,所以可以改变当前滑块的x为之前的x + 已经完成滚动的距离(xDistance * progress)
    scrollLine?.frame.origin.x = oldLabel.frame.origin.x + xDistance * progress
    
    这样就达到了滑块的位置随着提供的progress同步移动

    其他的遮盖 和颜色渐变的处理类似

b 内容显示部分View

基本属性

// 用于处理重用和内容的显示
@property (weak, nonatomic) UICollectionView *collectionView;
// collectionView的布局
@property (strong, nonatomic) UICollectionViewFlowLayout *collectionViewLayout;
/** 避免循环引用*/
@property (weak, nonatomic) ZJScrollSegmentView *segmentView;

@property (weak, nonatomic) UIButton *extraBtn;
// 父类 用于处理添加子控制器  使用weak避免循环引用
@property (weak, nonatomic) UIViewController *parentViewController;
// 当这个属性设置为YES的时候 就不用处理 scrollView滚动的计算
@property (assign, nonatomic) BOOL forbidTouchToAdjustPosition;
// 所有的子控制器
@property (strong, nonatomic) NSArray *childVcs;

逻辑处理

#pragma mark - life cycle 

- (instancetype)initWithFrame:(CGRect)frame childVcs:(NSArray *)childVcs segmentView:(ZJScrollSegmentView *)segmentView parentViewController:(UIViewController *)parentViewController {
    
    if (self = [super initWithFrame:frame]) {
        self.childVcs = childVcs;
        self.parentViewController = parentViewController;
        self.segmentView = segmentView;
        _oldIndex = 0;
        _currentIndex = 1;
        _oldOffSetX = 0.0;
        self.forbidTouchToAdjustPosition = NO;
        // 触发懒加载
        self.collectionView.backgroundColor = [UIColor whiteColor];
        [self commonInit];
    }
    return self;
}

/** 给外界可以设置ContentOffSet的方法 比如点击了标题的时候需要更改内容显示调用*/
- (void)setContentOffSet:(CGPoint)offset animated:(BOOL)animated

/** 给外界刷新视图的方法  用于动态设置子控制器和标题*/
- (void)reloadAllViewsWithNewChildVcs:(NSArray *)newChileVcs {
  // 这种处理是结束子控制器和父控制器的关系
    for (UIViewController *childVc in self.childVcs) {
        [childVc willMoveToParentViewController:nil];
        [childVc.view removeFromSuperview];
        [childVc removeFromParentViewController];
    }

}


#pragma mark - UICollectionViewDelegate --- UICollectionViewDataSource

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.childVcs.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
    // 移除subviews 避免重用内容显示错误
    [cell.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    // 这里建立子控制器和父控制器的关系  -> 当然在这之前已经将对应的子控制器添加到了父控制器了, 只不过还没有建立完成
    UIViewController *vc = (UIViewController *)self.childVcs[indexPath.row];
    vc.view.frame = self.bounds;
    [cell.contentView addSubview:vc.view];
    [vc didMoveToParentViewController:self.parentViewController];
    
    return cell;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView  -> 这个代理方法中处理了滚动的下标和进度  

3. "ZJScrollPageView.h"

这一部分是将segmentView和ContentView封装在一起的, 便于使用者直接使用, 但是您也可以参照这里面的实现, 任意组合segmentView和ContentView的位置, 在demo中也有示例


详细请移步OC源码, swift源码,里面都有详细的Demo使用示例 如果您觉得有帮助,不妨给个star鼓励一下, 欢迎关注


相关文章

网友评论

  • ZY:耦合度太高, 为什么不使用childViewController的方式呢?
  • 做一个如沐春风的girl:设置shouldAutomaticallyForwardAppearanceMethods这个之后,还是会出现生命周期不正常的情况。点击进入ZJVc3Controller,同时会加载ZJTestViewController这时没问题,点击ZJTestViewController中的button进入ZJTest2ViewController,这时ZJTestViewController的viewwillappear会走,viewWillDisappear、viewDidDisappear会走2遍 . 请问这个问题怎么解决?
  • 唯爱Q:您好,想问一下 怎么让太默认显示到第二个上边呢?
  • b2efe7751b24:我要设置前面三个是文字切换,最后一个是图标,怎么弄啊?
  • 我的天空蔚蓝色:当标签少的时候 ,标签的宽度没有适配屏幕!!!
  • RiberWang:大神,膜拜。如果这个视图在中间的话,我上下滑动子控制器,能不能实现悬停的效果。
  • a08ebadf4ba4:可不可以一进去就定义它最先显示在第一个标签
  • 235a008fb19c:请问大神这个item怎么放在下面 内容放在上面呢?
  • jzhang:如何单独改变某个标题的文字?比如原来是“体育(2)”改成“体育(2004”
  • 野地里的程序员怎么:大神!!为什么点击到中间那一页再点击其他页没反应??
  • 繁华乱世沧桑了谁的容颜:有BUG啊 , 在iOS 10.3.2的iPhone 6 Plus上面 标题就不显示 滑动才显示
  • d9303cb8f197:请问一下 怎么监听我滑动到下一页了呢? 以为我要在滑动到下一页的时候改一些UI
  • 烟花灬肆意:在界面上有输入框,键盘升上来,界面上滑界面就上去了下不来。
  • Karos_凯:实践时在拖拽即将看到下一页时会有点卡顿
  • icoder:类似新浪微博的头部悬浮出现下面tableview内容部分处于屏幕一下,若改变contenview的frame,就出现self.view 莫名的frame变化,未解决,希望博主给个招
  • 3be05ed9903c:我在使用此框架的时候发现一个问题不知道怎么解决了,当发生内存警告的时候页面上的数据就会突然清空了,这是怎么回事?
  • Yokihr:作者 您好,可以一进入界面的时候就把包括的子控制器都创建好么,不需要滑到或者点击某一个的时候在进行创建。
  • 涂浩Gavin:楼主,我在使用中发现一个bug,当style.adjustTitleWhenBeginDrag设置为YES的时候,滑动到下一个页面的一半,手不松开保持按住,然后再滑回当前页面(保证手松开的时候scrollView不会弹回,也就是刚刚好当前页完全展示出来),这时候将手松开,标题指向的是下一个页面,而内容还是当前页面,造成内容和标题不对应。
  • 夜之海澜:关于放大的问题,文字变模糊了,是怎么处理的呢
  • 李洛河:你好,请教一个问题,我这边的子控制器的是tableview ,当内存发出警告后,页面数据被清理掉,变成一片白?我这边的版本和github上的不一样,pod管理上去更新,跟原来的一样
  • 时光取名曰三千:大神你好,请问复用VC进行数据赋值的时候滑动之后调用哪一个方法呢?
  • 醉雨清风:你好,感谢提供这么方便的工具,使用中有一个问题,我childVC的生命周期没有正确的走,看了一下你的demo也是没有走。没有查出原因,方便给个联系方式吗?或者您加我的qq345144609,谢谢啦。 :smile:
    醉雨清风:@ZeroJ 我想用ZJScrollPageViewChildVcDelegate 可以吗?参考哪一个呢?ZJScrollPageViewDelegate是在父视图写的吗?这个感觉不太方便。 :blush:
    醉雨清风:@ZeroJ 好的,我再研究一下。
    ZeroJ:@醉雨清风 朋友, ZJScrollPageViewDelegate中关于生命周期的正确控制有说明, 你可以看一下, 或者参考ZJVc3中的示例
  • icoder:使用网络图片作为title 是出现图片显示很乱,没有宽度
  • fanqy:4S 真机,iOS 7.1,有问题出现
  • 丶Destinyxl:最新的 demo 在子控制器内点击button push、pop回来的时候 , 子控制器的控制器的生命周期方法 没走
  • 南门海少:请问大神,如果我有几十个各不相同的控制器要展示不同的数据和实现不同的功能,那么添加起来不是很繁琐?
    ZeroJ:@南门海少 朋友,这个就像tableView一样,如果cell的样式很多,那么使用的cell肯定就很多种了,这是不可避免的
  • Hyukooooh:] the behavior of the UICollectionViewFlowLayout is not defined because:
    2016-10-24 10:12:14.509 JiaZhangHuiApp[3902:643996] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
    2016-10-24 10:12:14.509 JiaZhangHuiApp[3902:643996] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7fa3ed4c47c0>, and it is attached to <UICollectionView: 0x7fa3eb81d000; frame = (0 0; 375 603); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x7fa3ed4c50c0>; layer = <CALayer: 0x7fa3ed4c4b80>; contentOffset: {375, 5.5}; contentSize: {750, 603}> collection view layout: <UICollectionViewFlowLayout: 0x7fa3ed4c47c0>.
    2016-10-24 10:12:14.509 JiaZhangHuiApp[3902:643996] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
    2016-10-24 10:12:14.509 JiaZhangHuiApp[3902:643996] the behavior of the UICollectionViewFlowLayout is not defined because:
    2016-10-24 10:12:14.510 JiaZhangHuiApp[3902:643996] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
    2016-10-24 10:12:14.510 JiaZhangHuiApp[3902:643996] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7fa3ed4c47c0>, and it is attached to <UICollectionView: 0x7fa3eb81d000; frame = (0 0; 375 603); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x7fa3ed4c50c0>; layer = <CALayer: 0x7fa3ed4c4b80>; contentOffset: {375, 0}; contentSize: {750, 377}> collection view layout: <UICollectionViewFlowLayout: 0x7fa3ed4c47c0>.
    2016-10-24 10:12:14.510 JiaZhangHuiApp[3902:643996] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
    这些错是怎么回事呀?
    ZeroJ:@LsyWtf 这个也不算个报错,只是collectionView在设置初始布局或者切换的过程中尺寸不恰当造成的,但对最终的结果影响没什么影响。不过你这个应该是之前的代码了,现在里面没有collectionView
  • 跑调的安眠曲:对于复用VC的数据加载问题,作者你的意思是将所有数据放在父VC中管理,然后在代理中根据对应的子VC来赋值就好了?
    跑调的安眠曲:@ZeroJ 被复用了的子VC不会被销毁吗?再次出现该VC的时候,岂不是又会去请求数据?
    ZeroJ:@跑调的安眠曲 这个,用一个不恰当的类比,子控制器的代理方法就和tableview里的cellforRow类似,不同cell的数据加载是在这一个方法里面根据indexPath不同加载的,那么类似的你应该在子控制器的代理方法里面利用index这些为不同页面加载数据
    ZeroJ:@跑调的安眠曲 不是这样的,数据仍然是在子控制器中管理的
  • b13cd3e68764:大神,有没空把label换成button,我尝试换了之后,button的图片文字显示不正确,要求上下居中 :sob:
    b13cd3e68764:@ZeroJ 不是按钮的呢 :sob:
    b13cd3e68764:@ZeroJ 谢谢大神
    ZeroJ:@wonin 已经更新了
  • 5636cc0d3f74:@ZeroJ 的确很好用,但在使用的时候遇到一个小问题,
    - (NSArray *)setupChildVcAndTitle {

    nsMutableArray * marr = [nsMutableArray array];
    for (int i = 0;i<_dataArray.count;i++)
    {
    BchinaViewController *vc1 = [BchinaViewController new];
    vc1.view.backgroundColor = [UIColor whiteColor];
    vc1.title = [NSString stringWithFormat:@"%@",_dataArray[i]];
    vc1.menuID = [NSString stringWithFormat:@"%@",_menuID[i]];

    [marr addobject vc1];
    }
    NSArray *childVcs = marr;
    return childVcs;
    }
    这里的VC1不能正向传值,怎么破,框架一样,只是urlStr的menu不同,在BchinaViewController里 数据为空
    ZeroJ:@畅游宇宙海 这样,你弄个小demo发到我Q上给你看看吧: 854136959
    5636cc0d3f74:很高兴看到你的回复,因为一直卡在这个地方,我不是在viewDidLoad里使用MenuID的;
    -(void)loadData
    {
    _urlStr = [NSString stringWithFormat:@"http://deng001.duapp.com/getAtcList?menuId=%@&page=%ld&size=10",_menuid,(long)_page];
    NSLog(@"memu = %@",_menuid);
    } 打印出来的结果还是为nil
    ZeroJ:@畅游宇宙海 你好, 朋友, 你这行代码 vc1.view,backgroundColor 就会调用BchinaViewController的viewDidLoad函数,如果你在viewDidLoad里面使用menuID就为nil
  • CoderChou:@ZeroJ 头部标题不适配,请问怎么回事
    ZeroJ:@菜鸟的尊严 :flushed: 这个不太可能吧
    CoderChou:@ZeroJ 就是正常情况下使用,出现问题了,标题有点问题
    ZeroJ:@菜鸟的尊严 暂时未适配旋转,国内大多数应用并不需要旋转
  • CoderChou:@ZeroJ 大神,小白请教下,代理方法通过index返回controller,为什么不返回view呢,这样做的优势是什么,期待您的回复
    ZeroJ:@菜鸟的尊严 :smile:想想navigationController为何不返回view给你
  • macfai:不错,大神,学习了
    macfai:好的,马上去,:+1:
    ZeroJ:@macfai :smile::smile:,刚刚更新了下,可以去玩玩新的
  • 伯陽:您好,我在使用这个的时候,import之后在
    ZJCustomLabel *firstLabel = (ZJCustomLabel *)self.titleLabels[0];
    这一句出现数组越界,不知能否请教如何解决
    ZeroJ:@伯陽 :relieved:,这不可能啊
  • 4d16197b657b:感谢分享
    ZeroJ:@4d16197b657b :grin:
  • 348bfbe0e20a:你好,请问一下,设置为标题不滚动时,可否让滚动条或者遮罩宽度适应标题文字宽度,而非等宽
    ZeroJ:@vayne_lee 已经更新了
    348bfbe0e20a:@ZeroJ 你好更新到了github吗
    ZeroJ:@vayne_lee 朋友, 我刚刚更新了代码, 实现了你说的效果
    1. 这种方法标题会平分宽度,但是遮盖和文字宽度一样
    // 同步调整遮盖或者滚动条的宽度
    style.adjustCoverOrLineWidth = YES;
    // 不滚动标题
    style.scrollTitle = NO;
    2. 这种方法标题不会平分宽度只是去除了scrollView的弹性效果
    style.segmentViewBounces = NO;
    style.scrollTitle = YES;
  • 君莫叹人生如若初见:学习学习,已关注
    ZeroJ:@EngineerMan :grin::grin:
  • 51bitquant:学习,编程思想确实不错。 :+1:
    ZeroJ:@StephenMark 增加什么功能,记得通知我一下,学习学习
    51bitquant:@ZeroJ 把你的修改了,增加一些功能。
    ZeroJ:@StephenMark :smile:不尽完美,供参考
  • DreamBuddy:大神你好!我想了解一下 如果有N个页面,在左右滑动导致view重用以后 ,当然页面的数据如何保存。。是否会重新加载
    ZeroJ:@iOS胖码农 :smile::smile:
    DreamBuddy:@ZeroJ 感谢!感悟了下,又看了您的代码,恍然大悟
    ZeroJ:@iOS胖码农 页面重用是会调用Vc的viewWillappear,至于数据怎么处理,是每个子控制器自己决定,是否要刷新,或者下拉刷新,同时,其实多个页面也可以公用一个控制器,只是需要在控制器里面管理不同的页面的view显示
  • HustBroventure:scrollview的滑动和侧滑返回的啊。最左边的页面可以使用滑动返回,不在最左边的页面,右滑动不应该触发滑动返回,而应该处理scrollview的滑动
    ZeroJ:@HustBroventure 因为两个手势触发的条件本来不一样,在系统的手势响应链中原则上说这两个系统的效果没有冲突,我只是不理解,为何你想要达到处理后的效果,你能否解释下这种效果的好处,然后我参考下是否要处理,多谢
    HustBroventure:如果所在的vc是root,那就不用处理啊。如果不是root,可以滑动返回的话,又使用了scrollview,就该处理两者手势冲突啊。
    ZeroJ:@HustBroventure 朋友,这个侧滑返回是系统的手势,而且,只在从边缘滑动的时候触发,所以每一个界面肯定是应该拥有这个效果的,为何你觉得应该处理?
  • HustBroventure:您好,发现一个问题。scrollView的滑动手势和页面返回的滑动手势冲突好像没有处理。
    ZeroJ:@HustBroventure 你好,这里面并没有自定义添加手势,不会存在手势冲突
  • 9efb0bc1399f:写的挺详细的,挺好的
    ZeroJ:@由木君 :smile: :smile: , 供参考参考
  • 方克己:一看你就是大神
    ZeroJ:@烧开的汽水 :smile: , 不敢当, 分享供学习, 不近完美, 供参考
  • jswift:感谢,分享, 之前看过你的那个swift版的感觉效果很好, 没想到这么快, 你就更新了oc版的,支持
    jswift:@ZeroJ 膜拜大神
    ZeroJ:@jswift :smile: :smile: , 客气了, 供参考

本文标题:OC版写一个快速集成网易新闻,腾讯视频,头条首页的ZJScrol

本文链接:https://www.haomeiwen.com/subject/tmfsrttx.html