PullDownStretchable 快速实现顶部背景下拉放大

前言

你的工程中是否有下面这种需求呢

  • 顶部背景视图下拉放大,上滑跟随滑走
  • 宽度可能跟随放大,也可能不变
  • 背景视图跨越多个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)
    }
}
相关推荐
席子哥哥的代码库17 小时前
自制简单的图片查看器(python)
开发语言·python·swift
打工人你好1 天前
Swift 的 KeyPath 是什么?
swift
struggle20252 天前
Ollmao (OH-luh-毛程序包及源码) 是一款原生 SwiftUI 应用程序,它与 Ollama 集成,可在 Mac 上本地运行强大的 AI 模型
ios·swiftui·swift
神仙别闹4 天前
基于Swift实现拼图游戏
开发语言·搜索引擎·swift
神仙别闹5 天前
基于Swift实现仿IOS闹钟
ios·cocoa·swift
ctf_02269 天前
echarts 3d中国地图飞行线
3d·echarts·swift
没头脑的ht9 天前
UITableView的复用原理
ios·swift
自不量力的A同学11 天前
苹果公司宣布正式开源 Xcode 引擎 Swift Build145
swift
SchneeDuan12 天前
iOS--SDWebImage源码解析
ios·缓存·swift·第三方库·sdwebimage
leluckys13 天前
swift 专题三 swift 规范一
开发语言·ios·swift