前景分析
当前项目中有两种类似的页面,一个是金贝商城中的购买页面,购买详情列表,第二次听一听中的列表页。
一种的构成是banner+菜单+内容。
另一种构成是菜单+内容。
CollectionView-Reload
这是金贝商城的购买页面。
通过Header上的按钮,刷新collectionView中的数据。
体验极差。
因为banner与menuView都在header中,不能固定,一旦下滑到底部,不能选择menuView。
在点击菜单栏之后,重新reload数据,无法记录之前浏览的位置。
不能左右滑动。
ASTopTabBarController
在听一听的首页中,自定义了一个ASTopTabBarController,特性等同于TabbarController。
相对于CollectionView的好处:
可以记录之前浏览的位置,顶部位置维持菜单栏。
但依然做不到左右滑动。
希望达到的效果
如图:
- 页面能够左右滑动
- 在返回时能记录之前滑动位置
- 菜单栏保持在最上部
- 点击菜单能正常滑动到指定位置(动画可有可无)
- 如果有banner也可以特殊处理
- 处理页面中各个VC的生命周期
UIPageViewController
UIPageViewController是系统提供的一个组件,切换页面时,支持滑动以及翻页两种方式。
系统给定的Api比较少,但是通过拆解,在一个VC中放入菜单栏与UIPageViewController,能完成1,2,3,6的基本需求。
但是因为UIPageViewController只能通过func setViewControllers([UIViewController]?, direction: UIPageViewController.NavigationDirection, animated: Bool, completion: ((Bool) -> Void)?)
的方式设定当前页面,会出现类似于push的动画效果显示指定页面。
UIPageViewController属性太少,不包含类似于bounces等属性。
由于这些缺陷UIPageViewController被放弃使用。
菜单栏+ScrollView
布局:
- 顶部定义一个固定位置的MenuView
- 剩余空间用一个ScrollView撑满
- ScrollView上添加一个StackView
- StackView中添加显示的View。
注意事项:
- 添加判断,不显示scrollview的inset。
if #available(iOS 11.0, *) {
rootScrollView.contentInsetAdjustmentBehavior = .never
} else {
automaticallyAdjustsScrollViewInsets = false
}
- 打开scrollview的isPagingEnabled
- 使scrollView对nav返回手势不响应,与bounces有一定关系。
if let popGesture = navigationController?.interactivePopGestureRecognizer {
//在scrollView的panGestureRecognizer响应情况下,不使用popGesture
popGesture.require(toFail: scrollView.panGestureRecognizer)
}
问题
VC的生命周期。
打开日志会发现在初始化时所有的VC都同时初始化,与embed的生命周期同步。
可以在滑动时调用beginAppearanceTransition
以及endAppearanceTransition
解决。
那么Vc,View应该什么时候放入到显示中?
- 一开始的layout时放入
- 滑动到当前页面时才添加
- 滑动到当前页面时才添加,离开之后需要移除
- 如果不移除会不会有内存上的问题,是否需要NSCache介入?
标题页+菜单栏+ScrollView(两层嵌套)
在之前的布局中,菜单栏是直接置于顶部,不需要滑动,与UIScrollview无关系好处理。
在现在的情况下, 标题栏和菜单栏都必须需要滑动。
布局:
- 底部用一个scrollview撑满屏幕。
- 头部空间添加一个Header,注意这里不是在scrollview上添加,而是在底层的view上添加。
- ScrollView上添加一个StackView,撑满屏幕。
- StackView中添加显示的View。
说明:
-
这种做法是底层的scrollview控制左右滑动,上层的tableview控制上下滑动,通过监听上层的tableview的offset,动态更新Header位置。
-
内容撑满屏幕是有缺陷的,但是必须撑满屏幕。如果不撑满屏幕,会导致header上移之后,多出的显示部分不会显示内容,解决方式是将内容页显示的页面内部的TableView或是Scrollview的类别添加顶部空白。
-
header需要重写响应链方式,以便手势可以穿透header滑动下层的ScrollView,如果header上有其他的点击事件也需要做特殊处理。
-
在二层嵌套时,左右滑动是rootScrollView控制,上下滑动由显示内容的页面控制(UITableView、UIColelctionView) ,最大的问题下拉刷新只能在
内容页
内实现。
标题页+菜单栏+ScrollView(三层嵌套)
布局:
- 底部用一个scrollview撑满屏幕。
- 在scrollview头部添加header,与menuView。
- 底部添加一个ScrollView,头部抵上menuView的底部。
- 底部的scrollview中添加添加一个StackView,撑满屏幕。
- StackView中添加显示的View。
需要重写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的闪现复位,采用了微博的处理方式,切换之后直接复位。