前言
你的工程中是否有下面这种需求呢
- 顶部背景视图下拉放大,上滑跟随滑走
- 宽度可能跟随放大,也可能不变
- 背景视图跨越多个Cell,顶部Cell种类不固定
PullDownStretchable
使用 PullDownStretchable 可以快速实现上面的需求
下面是 PullDownStretchable 接口及实现,你也可到 TableViewSections 工程中查看实现 PullDownStretchable.swift 和使用示例
swift
public protocol PullDownStretchable: class {
/// 被拉伸的视图
var stretchableView: UIView { get }
/// 适用于跟随滑动view的拉伸,在 scrollViewDidScroll 中调用,offset = scrollView.contentOffset.y
func stretch(with offset: CGFloat, scaleWidth: Bool)
/// 适用于固定view的拉伸
func stretchFixed(with offset: CGFloat, scaleWidth: Bool, ratio: CGFloat)
}
public extension PullDownStretchable {
func stretch(with offset: CGFloat, scaleWidth: Bool = true) {
if offset < 0 {
let scale = 1 - offset / stretchableView.bounds.height
let a = scaleWidth ? scale : 1
stretchableView.transform = CGAffineTransform(a: a, b: 0, c: 0, d: scale, tx: 0, ty: offset * 0.5)
} else {
stretchableView.transform = .identity
}
}
func stretchFixed(with offset: CGFloat, scaleWidth: Bool = true, ratio: CGFloat = 1) {
if offset < 0 {
let scale = 1 - offset / stretchableView.bounds.height
let a = scaleWidth ? scale : 1
stretchableView.transform = CGAffineTransform(a: a, b: 0, c: 0, d: scale, tx: 0, ty: -offset * 0.5 * ratio)
} else if offset == 0 {
stretchableView.transform = .identity
} else {
stretchableView.transform = CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 0, ty: -offset * ratio)
}
}
}
public extension PullDownStretchable where Self: UIView {
var stretchableView: UIView { return self }
}
如何使用?
在 viewController 中定义 stretchableView
其中有一个注意点:
当 tableView 不是 viewController.view 的根视图时,系统自动填充机制会有一些问题,我也没有深入研究,这里自己处理的 ContentInset
swift
class PlanType1ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .groupTableViewBackground
// 1. 放在 tableView 下面,作为背景
view.addSubview(topBgView)
topBgView.snp.makeConstraints {
$0.top.leading.width.equalToSuperview()
$0.height.equalTo(230)
}
view.addSubview(tableView)
// 2. tableView 不是根视图,需要自己处理内容填充,不然会产生奇怪的问题
tableView.contentInsetAdjustmentBehavior = .never
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
tableView.ps.item = PSLabelItem.empty(text: "加载中...")
.config { item in
item.layoutOffset = CGPoint(x: 0, y: -150)
}
// 3. 请求接口时隐藏 topBgView
topBgView.isHidden = true
viewModel.loadData { [weak tableView, weak topBgView] error in
// 4. 请求接口时隐藏 topBgView
topBgView?.isHidden = false
tableView?.ps.item = nil
}
}
let viewModel = PlanType1TableVM()
// 5. 定义顶部背景视图
private let topBgView = UIView().ns.config{
$0.backgroundColor = .orange
}
lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .grouped)
viewModel.associateTableView(tableView, viewController: self, additional: nil)
// ... 省略 部分代码
// 6. 背景色设置为头没,避免遮挡 topBgView
tableView.backgroundColor = .clear
return tableView
}()
// 7. 响应 safeAreaInsets 变更,调整 tableView.contentInset
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
print(view.safeAreaInsets)
tableView.contentInset = UIEdgeInsets(top: view.safeAreaInsets.top, left: 0, bottom: 0, right: 0)
}
}
// 8. 遵守 PullDownStretchable 协议
extension PlanType1ViewController: PullDownStretchable {
// 9. 返回可拉伸的view
var stretchableView: UIView {
return topBgView
}
}
响应 scrollViewDidScroll,调用拉伸方法
Demo 使用的 TableViewSections 架构组织 tableView 代码
PlanType1TableVM 作为 TableView 的 DataSourece、Delegate,通过 context 获取到 viewController
swift
class PlanType1TableVM: EstimatedTableViewModel {
// ... 省略无关代码
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// 根据 contentOffset 控制 stretchableView 形变
let continer = context.viewController as? PullDownStretchable
// scaleWidth == false,stretchableView 宽度不变
continer?.stretchFixed(with: scrollView.contentOffset.y + scrollView.adjustedContentInset.top, scaleWidth: false)
// scaleWidth == true,默认值,stretchableView 宽度改变
// continer?.stretchFixed(with: scrollView.contentOffset.y + scrollView.adjustedContentInset.top)
}
}