1. 基础版实现思路
1.1:层次结构

底部是一个UITableView,上面黄色部分为tableView的tableHeaderView,cell的数量为1,cell的contentView上防止了一个LTPageView, pageView上放置了一个scrollView且可以左右滑动分页,scrollView上放置控制器view,控制器view上放置各自的scrollView(tableView或collectionView)

1.2:使用方法(具体查看Demo)

(1)创建LTSimpleMabager,并添加到视图,传入frame,子控制器数组,标题数组,当前的控制器以及pageView的样式设置

LTSimpleManager(frame: <#T##CGRect#>, viewControllers: <#T##[UIViewController]#>, titles: <#T##[String]#>, currentViewController: <#T##UIViewController#>, layout: <#T##LTLayout#>)
(2)初始化子控制器的scrollView(tableView或collectionView),且tableView的y值从pageTitleView的高度开始,Demo中为44,具体可根据产品需求而定,tableView的height则为父view的高减去44

(3)将子控制器的scrollView(tableView或collectionView)赋值给glt_scollView即glt_scrollView = tableView,以下会说明原因。

1.3:实现思路

(1)当滑动底部tableView的时候,当tableView的contentOffset.y 小于 header的高的时候,将内容ScrollView的contentOffset设置为.zero

private func contentScrollViewScrollConfig(_ viewController: UIViewController) {
viewController.glt_scrollView?.scrollHandle = {[weak self] scrollView in
guard let `self` = self else { return }
self.contentTableView = scrollView
if self.tableView.contentOffset.y < self.kHeaderHeight {
scrollView.contentOffset = .zero;
scrollView.showsVerticalScrollIndicator = false
}else{
scrollView.showsVerticalScrollIndicator = true
}
}
}
(2)当滑动内容ScrollView的时候, 当内容contentOffset.y 大于 0(说明滑动的是内容ScrollView) 或者 当底部tableview的contentOffset.y大于 header的高度的时候,将底部tableView的偏移量设置为kHeaderHeight, 并将其他的scrollView的contentOffset置为.zero

public func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView == tableView, let contentTableView = contentTableView else { return }
let offsetY = scrollView.contentOffset.y
if contentTableView.contentOffset.y > 0.0 || offsetY > kHeaderHeight {
tableView.contentOffset = CGPoint(x: 0.0, y: kHeaderHeight)
}
if scrollView.contentOffset.y < kHeaderHeight {
for viewController in viewControllers {
guard viewController.glt_scrollView != scrollView else { continue }
viewController.glt_scrollView?.contentOffset = .zero
}
}
}
(3)headerView添加以及各个点击事件回调处理。

simpleManager.configHeaderView {[weak self] in
guard let strongSelf = self else { return nil }
let headerView = strongSelf.testLabel()
return headerView
}
simpleManager.didSelectIndexHandle { (index) in

}
simpleManager.refreshTableViewHandle { (scrollView, index) in
scrollView.mj_header = MJRefreshNormalHeader {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
scrollView.mj_header.endRefreshing()
})
}
}
2.进阶版实现思路
2.1:层次结构

底部是LTPageView, pageView上放置了一个scrollView且可以左右滑动分页,scrollView上放置控制器view,headerView是一个单独的View在顶部,利用scrollView的contentInset将其放置在了*上面

2.2:使用方法(具体查看Demo)

(1)创建LTAdvancedManager,并添加到视图,传入frame,子控制器数组,标题数组,当前的控制器以及pageView的样式设置和自己的headerView(在闭包中返回即可)

LTAdvancedManager(frame: <#T##CGRect#>, viewControllers: <#T##[UIViewController]#>, titles: <#T##[String]#>, currentViewController: <#T##UIViewController#>, layout: <#T##LTLayout#>, headerViewHandle: <#T##() -> UIView#>)
(2)初始化子控制器的scrollView(tableView或collectionView),且tableView的y值从0开始,tableView的height则为父view的高减去44

(3)将子控制器的的scrollView(tableView或collectionView)赋值给glt_scollView即glt_scrollView = tableView,以下会说明原因。

2.3:实现思路

(1)主要是利用子控制的滚动来控制headerView

//MARK: 当前控制器的滑动方法事件处理
private func contentScrollViewDidScroll(_ contentScrollView: UIScrollView, _ absOffset: CGFloat) {

//获取当前控制器
let currentVc = viewControllers[currentSelectIndex]

//外部监听当前ScrollView的偏移量
self.delegate?.glt_scrollViewOffsetY?((currentVc.glt_scrollView?.contentOffset.y ?? kHeaderHeight) + self.kHeaderHeight + layout.sliderHeight)

//获取偏移量
let offsetY = contentScrollView.contentOffset.y

//获取当前pageTitleView的Y值
var pageTitleViewY = pageView.pageTitleView.frame.origin.y

//pageTitleView从初始位置上升的距离
let titleViewBottomDistance = offsetY + kHeaderHeight + layout.sliderHeight

let headerViewOffset = titleViewBottomDistance + pageTitleViewY

if absOffset > 0 && titleViewBottomDistance > 0 {//向上滑动
if headerViewOffset >= kHeaderHeight {
pageTitleViewY += -absOffset
if pageTitleViewY <= hoverY {
pageTitleViewY = hoverY
}
}
}else{//向下滑动
if headerViewOffset < kHeaderHeight {
pageTitleViewY = -titleViewBottomDistance + kHeaderHeight
if pageTitleViewY >= kHeaderHeight {
pageTitleViewY = kHeaderHeight
}
}
}

pageView.pageTitleView.frame.origin.y = pageTitleViewY
headerView?.frame.origin.y = pageTitleViewY – kHeaderHeight
let lastDiffTitleToNavOffset = pageTitleViewY – lastDiffTitleToNav
lastDiffTitleToNav = pageTitleViewY
//使其他控制器跟随改变
for subVC in viewControllers {
guard subVC != currentVc else { continue }
guard let vcGlt_scrollView = subVC.glt_scrollView else { continue }
vcGlt_scrollView.contentOffset.y += (-lastDiffTitleToNavOffset)
subVC.glt_upOffset = String(describing: vcGlt_scrollView.contentOffset.y)
}
}
3.问题以及补充
3.1:问题1:全局监听scrollView的滚动

实现思路是根据runtime,交换方法的实现,主要的坑就是当时采用的交换scrollViewDidScroll(_:),但它是UIScrollViewDelegate的协议方法,发现行不通,后来找到了UIScrollView中的一个方法Selector((“_notifyDidScroll”)),发现拦截它是可行的,当然Swift中拦截方法,也是有很多坑的,和OC有很大不同,具体操作看源码吧!

3.2:进阶版遗留小问题

当headerView中有按钮需要响应事件的时候,我这里采用的是利用override func point(inside point: CGPoint, with event: UIEvent?) -> Bool方法进行判断,因为上面headerView的交互只有关闭了才能滑动headerView左右切换,所以在这里我只允许要响应事件的控件交互打开了,其他区域交互关闭。如果有更好的思路欢迎联系!!!不胜感激!

//MARK: 暂用,待优化。
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for tempView in self.subviews {
if tempView.isKind(of: UILabel.self) {
let button = tempView as! UILabel
let newPoint = self.convert(point, to: button)
if button.bounds.contains(newPoint) {
return true
}
}
}
return false
}