多重ScrollView叠加场景

1,318 阅读4分钟

前景分析

当前项目中有两种类似的页面,一个是金贝商城中的购买页面,购买详情列表,第二次听一听中的列表页。

一种的构成是banner+菜单+内容。

另一种构成是菜单+内容。

CollectionView-Reload

这是金贝商城的购买页面。

通过Header上的按钮,刷新collectionView中的数据。

体验极差。

因为banner与menuView都在header中,不能固定,一旦下滑到底部,不能选择menuView。

在点击菜单栏之后,重新reload数据,无法记录之前浏览的位置。

不能左右滑动。

reload

ASTopTabBarController

在听一听的首页中,自定义了一个ASTopTabBarController,特性等同于TabbarController。

switch

相对于CollectionView的好处:

可以记录之前浏览的位置,顶部位置维持菜单栏。

但依然做不到左右滑动。

希望达到的效果

如图:

Demo1

Demo2

  1. 页面能够左右滑动
  2. 在返回时能记录之前滑动位置
  3. 菜单栏保持在最上部
  4. 点击菜单能正常滑动到指定位置(动画可有可无)
  5. 如果有banner也可以特殊处理
  6. 处理页面中各个VC的生命周期

UIPageViewController

UIPageViewController是系统提供的一个组件,切换页面时,支持滑动以及翻页两种方式。

Page

系统给定的Api比较少,但是通过拆解,在一个VC中放入菜单栏与UIPageViewController,能完成1,2,3,6的基本需求。

但是因为UIPageViewController只能通过func setViewControllers([UIViewController]?, direction: UIPageViewController.NavigationDirection, animated: Bool, completion: ((Bool) -> Void)?)的方式设定当前页面,会出现类似于push的动画效果显示指定页面。

UIPageViewController属性太少,不包含类似于bounces等属性。

由于这些缺陷UIPageViewController被放弃使用。

菜单栏+ScrollView

布局:

  1. 顶部定义一个固定位置的MenuView
  2. 剩余空间用一个ScrollView撑满
  3. ScrollView上添加一个StackView
  4. StackView中添加显示的View。

Menu

注意事项:

  1. 添加判断,不显示scrollview的inset。
 if #available(iOS 11.0, *) {
     rootScrollView.contentInsetAdjustmentBehavior = .never
 } else {
     automaticallyAdjustsScrollViewInsets = false
 }
  1. 打开scrollview的isPagingEnabled
  2. 使scrollView对nav返回手势不响应,与bounces有一定关系。
 if let popGesture = navigationController?.interactivePopGestureRecognizer {
	   //在scrollView的panGestureRecognizer响应情况下,不使用popGesture
     popGesture.require(toFail: scrollView.panGestureRecognizer)
 }

问题

VC的生命周期。

打开日志会发现在初始化时所有的VC都同时初始化,与embed的生命周期同步。

可以在滑动时调用beginAppearanceTransition以及endAppearanceTransition解决。

那么Vc,View应该什么时候放入到显示中?

  1. 一开始的layout时放入
  2. 滑动到当前页面时才添加
  3. 滑动到当前页面时才添加,离开之后需要移除
  4. 如果不移除会不会有内存上的问题,是否需要NSCache介入?

标题页+菜单栏+ScrollView(两层嵌套)

在之前的布局中,菜单栏是直接置于顶部,不需要滑动,与UIScrollview无关系好处理。

在现在的情况下, 标题栏和菜单栏都必须需要滑动。

布局:

  1. 底部用一个scrollview撑满屏幕。
  2. 头部空间添加一个Header,注意这里不是在scrollview上添加,而是在底层的view上添加。
  3. ScrollView上添加一个StackView,撑满屏幕。
  4. StackView中添加显示的View。

Double

说明:

  • 这种做法是底层的scrollview控制左右滑动,上层的tableview控制上下滑动,通过监听上层的tableview的offset,动态更新Header位置。

  • 内容撑满屏幕是有缺陷的,但是必须撑满屏幕。如果不撑满屏幕,会导致header上移之后,多出的显示部分不会显示内容,解决方式是将内容页显示的页面内部的TableView或是Scrollview的类别添加顶部空白。

  • header需要重写响应链方式,以便手势可以穿透header滑动下层的ScrollView,如果header上有其他的点击事件也需要做特殊处理。

  • 在二层嵌套时,左右滑动是rootScrollView控制,上下滑动由显示内容的页面控制(UITableView、UIColelctionView) ,最大的问题下拉刷新只能在内容页 内实现。

标题页+菜单栏+ScrollView(三层嵌套)

布局:

  1. 底部用一个scrollview撑满屏幕。
  2. 在scrollview头部添加header,与menuView。
  3. 底部添加一个ScrollView,头部抵上menuView的底部。
  4. 底部的scrollview中添加添加一个StackView,撑满屏幕。
  5. StackView中添加显示的View。

Triple

需要重写rootScrollView的手势穿透事件:

    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        
        guard let scrollView = gestureRecognizer.view as? UIScrollView else {
            return false
        }
        
        let offsetY = headerViewHeight + menuViewHeight
        let contentSize = scrollView.contentSize
        let targetRect = CGRect(x: 0,
                                y: offsetY,
                                width: contentSize.width,
                                height: contentSize.height - offsetY)
        
        let currentPoint = gestureRecognizer.location(in: self)
        return targetRect.contains(currentPoint)
    }

在滑动的时候如果只剩下菜单栏,防止继续滑动,需要将设置为rootScrollView的bounce设置为false。

已经滑动到顶部的视图,切换到在已经滑动一半时,上下继续滚动会出现header的闪现复位,采用了微博的处理方式,切换之后直接复位。