美文网首页程序员程序猿阵线联盟-汇总各类技术干货
iOS开发-类似微博个人主页效果解决方案之一

iOS开发-类似微博个人主页效果解决方案之一

作者: Kegem | 来源:发表于2018-06-05 19:45 被阅读42次

    微博主页效果指的是多个包含UIScrollViewUITableView/UICollectionView)的控制器或view在同一个控制器中展示。

    • 主控制器包含UITableView支持下拉放大,包含PageView等类似的控件,解决主控制器中UITableView的滑动和PageView滑动的冲突;
    • 多个子控制器都包含UITableViewUICollectionView,解决子控制器中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上了,希望能帮到大家

    相关文章

      网友评论

      • Kegem:已经在github上提供了demo,并支持pod导入使用
      • 05b767affff8:需要demo
        Kegem:明天有时间的话 我简单写一个

      本文标题:iOS开发-类似微博个人主页效果解决方案之一

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