多页面混合开发利器之UIPageViewController

panda.jpeg

疑问

不知道大家发开时有没遇到这样一个需求,一个页面当中可以进行条件或者内容切换然后进行不同详情内容的显示,当内容样式差不多的时候我们还可以进行替换数据源和不同的 UITableViewCell 进行处理。但是,当我们遇到每个详情内容页面的风格差异很大的时候该如何处理呢?比如下面这组图(图片来源于手机京东iOS客户端)

1.jpg
2.png

好吧,也许有些人看这样的界面说我可以做到,不就是简单的欢欢换换数据源和替换不同样式的 UITableViewCell 或者 UICollectionViewCell 吗?但是我弱弱地问下,如果这个界面包含了五种?六种或者更多的不同数据源呢?每个界面的业务逻辑稍有差异,在这样的情况下还采取这样的开发方式........................=.=(心中一万只草泥马跑过)这样既违反了软件编程本身的低耦合高内聚,同时也不便于日后的维护和扩展。

代替方案

思路

想一下,其实就算我们是一个单一的页面时,页面上还是不是依旧充满了许多控件吗?我们可以利用这种封装的思想,将我们不同要显示的具体子界面拆分成不同的 UIViewController.view 进行显示,既然思路有了那就该着手实现的具体方案了,目前我所想到的有两种方案,如果你还有什么更好的方案欢迎指出:

1.使用 UIPageViewController 进行管理
2.在 UIScrollView 的基础上自行加载各自 UIViewController.view 并进行相对应的管理

接下来我会在这种两种方案上进行对比分析,既然今天的主角是 UIPageViewController 那我们就先开始介绍它吧,不然各位看官看了那么多废话还没进入主题 ,Let's do IT ^ . ^

UIPageViewController

介绍

0.png

此控件为使用者提供了在不同内容界面之间跳转功能,并且不同界面之间管理着自己独自的 UIViewController,同时该控件不仅已经为用户提供了跳转手势(滑动)也支持代码上的跳转管理,而且用户也可指定界面之间的跳转动画方式


听起来蛮符合我们的要求的嘛😄

1.初始化
3.png

初始化系统提供了两种翻页动画:
UIPageViewControllerTransitionStylePageCurl = 0, 类书籍翻页效果
UIPageViewControllerTransitionStyleScroll = 1 滚动效果

[self addChildViewController:self.pageViewController];
[self.pageViewController didMoveToParentViewController:self];
    
[self.view addSubview:self.pageViewController.view];
- (UIPageViewController *)pageViewController {
    
    if (!_pageViewController) {
        
        _pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
                                                              navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                                                                            options:nil];
        _pageViewController.view.frame = CGRectMake(0, 64, SCREEN_WIDTH, SCREEN_HEIGHT - 64);
        _pageViewController.dataSource = self;
        _pageViewController.delegate = self;
        [_pageViewController setViewControllers:@[self.childViewControllersArray[_currentIndex]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
    }
    return _pageViewController;
}

初始化的时候需要设置一个当前显示 UIViewController,在这里我是利用一个数组管理所有的子页面 childViewControllersArray

- (NSArray<UIViewController *> *)childViewControllersArray {
    
    if (!_childViewControllersArray) {
        
        _childViewControllersArray = @[[[ViewControllerA alloc] init],
                                       [[ViewControllerB alloc] init],
                                       [[ViewControllerC alloc] init],
                                       [[ViewControllerD alloc] init]];
    }
    return _childViewControllersArray;
}
2.设置代理
#pragma mark - UIPageViewControllerDataSource
//当前界面的上一个界面,该代理在手势操作时便触发(轻微滑动),并且应该是有某种缓存机制,同一界面的第二次手势操作不触发
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
    
    NSInteger beforeIndex = _currentIndex - 1;
    //返回nil时禁止继续滑动
    if (beforeIndex < 0) return nil;
    
    return self.childViewControllersArray[beforeIndex];
}

//当前界面的下一个界面,机理同上
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
    
    NSInteger afterIndex = _currentIndex + 1;
    if (afterIndex > self.childViewControllersArray.count - 1) return nil;

    return self.childViewControllersArray[afterIndex];
}
#pragma mark - UIPageViewControllerDelegate
//跳转动画开始时触发,利用该方法可以定位将要跳转的界面
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers {
    //pendingViewControllers虽然是一个数组,但经测试证明该数组始终只包含一个对象
    _pengdingViewController = pendingViewControllers.firstObject;
}

//跳转动画完成时触发,配合上面的代理方法可以定位到具体的跳转界面,此方法有利于定位具体的界面位置(childViewControllersArray),便于日后的管理
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed {
    //previousViewControllers虽然是一个数组,但经测试证明该数组始终只包含一个对象
    if (completed) {
        
        [self.childViewControllersArray enumerateObjectsUsingBlock:^(UIViewController * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            if (_pengdingViewController == obj) {
                
                _currentIndex = idx;
                *stop = YES;
            }
        }];
    }
}

代理设置完成同时自定义了一个顶部选择栏来达到类似手机京东的效果,自定义控件的细节这里就不细讲了,具体可以看最后放出的 Demo

最后效果图
UIPageVIewController.gif
运行Demo并验证相关疑问

1.各种的子界面是否会在 APP 加载完成时就完全加载呢?
为了验证这个问题,我们将对所有子 UIViewControllerviewDidLoad 方法进行监控:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor colorWithRed:arc4random()%256/255.0 green:arc4random()%256/255.0 blue:arc4random()%256/255.0 alpha:1.0];
    NSLog(@"%@ %s", NSStringFromClass([self class]), __func__);
}

结果:

4.png

可以看到在 APP 完成初始化的时候只有第一个子UIViewController调用了 ** viewDidLoad** 进行了具体相关加载,我们再左右滑动看下结果如何

5.png

只有显示了的子 UIViewControllerAPP 才会进行相关内容的加载,这类似于懒加载机制,节约了对系统内存的开销


基于UIScrollerView实现

现在再来让我们看看第二种方案的结果如何
具体实现还是比较简单的知识利用了 UIScrollerView 加载各种子 UIViewController,具体代码实现如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    _currentIndex = 0;
    
#ifdef usingPageViewController
    
    [self addChildViewController:self.pageViewController];
    [self.pageViewController didMoveToParentViewController:self];
    
    [self.view addSubview:self.pageViewController.view];
#else

    [self.view addSubview:self.scrollerView];
#endif
    [self.view addSubview:self.segmentView];
}
- (UIScrollView *)scrollerView {
    
    if (!_scrollerView) {
        
        _scrollerView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 64, SCREEN_WIDTH, SCREEN_HEIGHT - 64)];
        _scrollerView.contentSize = CGSizeMake(SCREEN_WIDTH * self.childViewControllersArray.count, SCREEN_HEIGHT - 64);
        _scrollerView.pagingEnabled = YES;
        __weak typeof(self) weakSelf = self;
        [self.childViewControllersArray enumerateObjectsUsingBlock:^(UIViewController * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            obj.view.frame = CGRectMake(SCREEN_WIDTH * idx, 0, SCREEN_WIDTH, SCREEN_HEIGHT - 64);
            [weakSelf addChildViewController:obj];
            [obj didMoveToParentViewController:weakSelf];
            [_scrollerView addSubview:obj.view];
        }];
    }
    return _scrollerView;
}

Demo 中已经利用预编译命令实现好了这两种布局方法,大家只要开放或者取消这个宏定义即可

ViewController.m

#define usingPageViewController

在布局方案变更后再回到我们之前的那个疑问中,看下此种方案的系统加载流程是如何的:

6.png

可以看到在 APP 初始化完成时,各个子 UIViewController 也完成了其自身的加载,如果子 UIViewController 内容相对复杂的情况下,此种方案对于系统的各项开销还是比较大的

总结

1.无论利用 UIPageViewController 还是 UIScroller 都很好的降低了主界面的耦合性,各自子 UIViewController 都维护着自己的单一模块的功能, UI 等(在包含两个布局方案下,主视图 ViewController 代码行数只有 170 行)
2.但是由于 ** UIPageViewController** 在懒加载的加持下,很好的优化了系统对于内存等方面的压力
3.UIPageViewController 不仅默认提供了两种不同的翻页动画效果还对用户滑动手势有了很好的支持,而且通过其提供的代理回调方法我们也能很便利的对当前滑动页进行管理和监测,这大大提高了工作效率和降低了降低了研发成本

本人也是新手入门如果发现本文有什么错误的地方可以下方留言跟我反馈,哪怕是文章的排版问题还是其他影响到阅读体验的问题都可以反馈我,大家一起探讨如果我确实写的有问题我会欣然接受并改正。同时我的新浪微博是 KeepMoveingOn,有任何问题也都可以@我,我会尽快回复大家的,感谢各位看官花费宝贵的时间阅读此文🙏

Demo地址

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

推荐阅读更多精彩内容