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 

@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 *)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'
效果展示

你可能感兴趣的:(iOS开发-类似微博个人主页效果解决方案之一)