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

微博主页效果指的是多个包含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上了,希望能帮到大家
同时也支持pod导入到项目使用

pod 'KJMultipleScrollViewManager'
效果展示
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,937评论 3 118
  • 东湖形胜鲜人知, 景比西湖实更奇。 露重樱花随地落, 风轻莲叶漫湖依。 湖光漭漭云接水, 山势团团树作堤...
    东林梁阅读 837评论 10 35
  • “我对死亡感到唯一的痛苦,是没能为爱而死。” ...
    梦想家尼可阅读 8,874评论 2 7
  • 已到而立之年,不懂爱与非爱!不知道是命运的安排,还是你故意让我等待!我是一个情窦晚来的姑娘,大学以前不觉得男女有什...
    玉波清清阅读 166评论 0 0
  • 小费送我到火车站,千叮咛万嘱咐后才放心离去。我在昏昏欲睡中坐上了k8401,苏州开往杭州的最后一趟夜班车,过道拥挤...
    十一渡阅读 472评论 0 6