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)
    }
}
相关推荐
君陌笑10 小时前
iOS-小说阅读器功能拆分之笔记划线
ios·swift
FreeCultureBoy1 天前
Swift 与 SwiftUI 学习系列:变量与常量篇 🚀
swiftui·xcode·swift
FreeCultureBoy1 天前
Swift 与 SwiftUI 学习系列: print 函数详解 🚀
swiftui·xcode·swift
依旧风轻1 天前
使用AES加密数据传输的iOS客户端实现方案
ios·swift·aes·network
依旧风轻2 天前
精确计算应用的冷启动耗时
ios·swift·cold start
hzgisme3 天前
iOS 视图实现渐变色背景
ios·cocoa·swift
大熊猫侯佩3 天前
Swift 中强大的 Key Paths(键路径)机制趣谈(上)
swift·序列·语法糖·键路径·keypath·转换为方法·协议扩展
V言微语3 天前
2.2.3 C#中显示控件BDPictureBox 的实现----控件实现
开发语言·c#·swift
concisedistinct4 天前
探索iOS开发语言基础与Xcode工具:从零开始构建你的第一个iOS应用
开发语言·ios·objective-c·xcode·swift