swift-oc混编分享2-tableView优化

本文介绍了在iOS开发中,如何通过创建HTableViewManager等类,利用Swift和Objective-C实现表格视图的高效管理,包括高度计算、事件回调的简化,以及如何通过预先计算和封装减少代码量,适用于复杂场景的TableView优化。
摘要由CSDN通过智能技术生成

软件环境:Xcode 13.2、swift 5
创建时间:2022年 03月10号
适用范围:iOS项目

这篇文章都说了什么

  1. 在iOS中,如何优化tableView,提升效率(高度计算、事件回调),降低代码量
  2. 源码分享(swift+OC),快速在你的项目中搭建

背景

         tableView是我们比较常用的容器类组件,每个页面,无论是否超过一屏,使用scrollView的弹性效果,能更贴合iPhone温柔的用户体验。所以,app的大部分页面几乎都要用到tableView。        

        假如有下面这样一个页面,其中某组数据在一些场景中可能有也可能没有,如果用tableView原生来实现,两个主要的代理函数的代码写起来会很复杂,判断每个indexPath,设置不同cell。

图片太长 点击查看

        结合table复用的特点,table的内容展示、cell高度、section头等信息如果每次滚动都需要计算,就会耗费一定的性能,而且页面复杂的时候,代码量很大。所以如果我们能一次性计算完成,然后让table的代理直接调用计算后的结果,会好一些。本文就以减少频繁计算、降低编码难度为出发点,来对tableView做一层包装。

以下,以swift代码为例


一、主要思路

如果不想每次滚动都计算高度和查找cell,那就把高度和cell与indexPath的关系通过一个管理类管理起来,比如有多少个section,每个section包含哪些cell,每个cell的高度是多少、依赖哪些数据、点击事件回调等都设置好

二、主要类及其功能

1、概要

我们结合表格来看一下这个管理工具各个类的作用

类名作用

HTableViewManager

管理主类,实现tableView的代理,用来把下面的模型类转翻译并传给tableView的代理函数,主要对tableView的基本属性(布局样式等)及section进行管理
HTableViewSectionModel匹配tableView的section,主要包含对tableView的row进行管理
HTableViewRowModel匹配tableView的row,主要包含对row的高度、数据、交互事件的管理
HTableViewSectionHeaderModel匹配tableView的“viewForHeaderInSection”,控制每组的头
HTableViewRowEditingModel匹配tableView的editingStyleForRowAt IndexPath,控制每行的编辑样式

HBaseTableViewCell 

UITableViewCell的子类,从HTableViewManager中获取数据、交互、回调等

HBaseSeparateTableViewCell

HBaseTableViewCell的子类,简易分割线,把一个cell当成分割线,减少代码量,部分场景适用,可选。

我们只要把每一块的数据都按照展示顺序以HTableViewRowModel的实例装入HTableViewSectionModel里面,然后把页面依赖的tableViewCell画出来,然后交给HTableViewManager去管理和展示,而无需关注具体管理细节。


结合下图,我们立体的看一下各个类之间的关系

结合UI展示,看看整体结构关系

如果看了这些,你还不清楚整体逻辑关系,就保留一些疑问,卡看具体代码吧


2、具体介绍

HTableViewManager,实现tableView的代理函数,管理sections,从sections里面获取到一共有多少个分组,展示每个分组的row及其编辑效果、展示每个分组的header

import UIKit

/// 代理
@objc public protocol HTableViewManagerDelegate : NSObjectProtocol {
    
    @objc optional func tableViewDidScroll(_ tableView: UITableView)
}

/// 数据
@objc public protocol HTableViewManagerDataSource : NSObjectProtocol {
    
    @objc optional func tableViewDataSource(_ tableManager: HTableViewManager)
}

open class HTableViewManager: NSObject, UITableViewDataSource, UITableViewDelegate {

    weak var delegate :HTableViewManagerDelegate?
    
    weak var dataSource :HTableViewManagerDataSource?
    
    var tableView: UITableView?
    
    /// sections with rows
    lazy var sections: NSMutableArray = {
        let sections = NSMutableArray.init()
        return sections
    }()
    
    public init(tableView: UITableView, dataSource: HTableViewManagerDataSource) {
        super.init()
        self.tableView = tableView
        self.tableView?.delegate = self
        self.tableView?.dataSource = self
        self.dataSource = dataSource
        self.tableView?.separatorStyle = .none
        self.tableView?.showsVerticalScrollIndicator = false
        if #available(iOS 11.0, *) {
            self.tableView?.contentInsetAdjustmentBehavior = .never
        }
        if #available(iOS 15.0, *) {
            self.tableView?.sectionHeaderHeight = 0
        }
    }
    
    // MARK: func
    
    /// 添加一个 section
    func addSection(_ section: HTableViewSectionModel) {
        sections.add(section)
    }
    
    /// 指定位置插入section
    func insertSection(_ section: HTableViewSectionModel, at index: NSInteger) {
        sections.insert(section, at: index)
    }
    
    /// 清空所有
    func removeAllSection() {
        sections.removeAllObjects()
    }
    
    /// 直接添加row(添加到最后一个section,如果没有section会创建一个section)
    func addRow(_ row: HTableViewRowModel) {
        var lastSection: HTableViewSectionModel
        if sections.count > 0 {
            lastSection = sections.lastObject as! HTableViewSectionModel
        } else {
            lastSection = HTableViewSectionModel()
            sections.add(lastSection)
        }
        lastSection.addRow(row)
    }
    
    /// 批量添加row,添加到最后一个section
    func addRows(_ rows: [HTableViewRowModel]) {
        var lastSection: HTableViewSectionModel
        if sections.count > 0 {
            lastSection = sections.lastObject as! HTableViewSectionModel
        } else {
            lastSection = HTableViewSectionModel()
            sections.add(lastSection)
        }
        lastSection.rows.addObjects(from: rows)
    }
    
    /// 刷新(相当于执行tableView.reloadData(),此时会调用tableViewDataSource重新获取数据)
    func reloadTableView() {
        sections.removeAllObjects()
        dataSource?.tableViewDataSource?(self)
        tableView?.reloadData()
    }
    
    
    // MARK: table data source
    public func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }
    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        let sectionModel :HTableViewSectionModel = sections[section] as! HTableViewSectionModel
        
        return sectionModel.rows.count
    }
    
    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let section :HTableViewSectionModel = sections[indexPath.section] as! HTableViewSectionModel
        let row :HTableViewRowModel = section.rows[indexPath.row] as! HTableViewRowModel
        
        //cell
        var cell = tableView.dequeueReusableCell(withIdentifier: row.reuserIdentifier)
        if cell == nil {
            let newCell :HBaseTableViewCell.Type = row.cellClasss as! HBaseTableViewCell.Type
            cell = newCell.init(style: UITableViewCell.CellStyle.default, reuseIdentifier: row.reuserIdentifier)
        }
        
        (cell as! HBaseTableViewCell).row = row
        (cell as! HBaseTableViewCell).cellWillAppear()
        
        return cell!
    }
    
    // MARK: delegate ------------------
    // MARK: basic
    public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let section :HTableViewSectionModel = sections[indexPath.section] as! HTableViewSectionModel
        let row :HTableViewRowModel = section.rows[indexPath.row] as! HTableViewRowModel
        return CGFloat(row.rowHeight)
    }
    
    /// 支持 section header
    public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let sectionModel :HTableViewSectionModel = sections[section] as! HTableViewSectionModel
        if sectionModel.headerView == nil {
            return nil
        } else {
            return sectionModel.headerView?.headerView
        }
    }
    
    public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        let sectionModel :HTableViewSectionModel = sections[section] as! HTableViewSectionModel
        if sectionModel.headerView == nil {
            return 0
        } else {
            return CGFloat((sectionModel.headerView?.headerHeight)!)
        }
    }
    
    /// 支持编辑
    public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        let section :HTableViewSectionModel = sections[indexPath.section] as! HTableViewSectionModel
        let row :HTableViewRowModel = section.rows[indexPath.row] as! HTableViewRowModel
        if row.editingStyles.count == 0 {
            return .none
        } else {
            return .delete
        }
    }
    public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let section :HTableViewSectionModel = sections[indexPath.section] as! HTableViewSectionModel
        let row :HTableViewRowModel = section.rows[indexPath.row] as! HTableViewRowModel
        if row.editingStyles.count == 0 {
            return []
        } else {
            var actions :[UITableViewRowAction] = []
            var index = 0
            for modelx in row.editingStyles {
                let model = (modelx as! HTableViewRowEditingModel)
                let action = UITableViewRowAction.init(style: .default, title: model.title) { (action, indexPath) in
                    row.didEditRowWithActionAtIndex(index)
                }
                action.backgroundColor = model.backgroundColor
                actions.append(action)
                index += 1
            }
            return actions
        }
    }
    
    /// 代理滚动
    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        delegate?.tableViewDidScroll?(tableView!)
    }
    
    // 其他代理,按需补充
    // 按需刷新,感兴趣自己试试

HTableViewSectionModel,管理每个分组的rows


/// sectiion data model
open class HTableViewSectionModel: NSObject {
    
    /// 添加一行
    public func addRow(_ row: HTableViewRowModel) {
        rows.add(row);
    }
    
    /// 添加分割线
    public func addSeparateRowWithHeight(height: Double, color: UIColor) {
        let row = HTableViewRowModel.separateRow(height: height, color: color);
        addRow(row)
    }
    
    /// 插入一行
    public func insertRow(row: HTableViewRowModel, atIndex: Int) {
        rows.insert(row, at: atIndex)
    }
    
    /// 删除一行
    public func deleteRowAtIndex(_ index: Int) {
        rows.removeObject(at: index)
    }
    
    /// 所有行
    lazy var rows: NSMutableArray = {
        let rows = NSMutableArray.init()
        return rows
    }()
    
    /// section header
    var headerView :HTableViewSectionHeaderModel?
    
}

HTableViewRowModel,包含每个row用哪个UITableViewCell来展示、行高、数据(多种类型可选)、事件回调(多种方式可选)

import UIKit


/// row data model
open class HTableViewRowModel: NSObject {
    
    override public init() {
        super.init()
        model = "" as AnyObject
    }
    
    static func row() ->HTableViewRowModel {
        return HTableViewRowModel.init()
    }
    
    /// 行高
    var rowHeight = 44.0
    
    /// cellClasss
    var cellClasss: AnyClass = HBaseTableViewCell.self
    
    /// 模型
    public var model : AnyObject?
    
    /// 备用模型
    public var subModel : AnyObject?
    
    /// 字符串model
    public var strModel : String?
    
    /// 整型model
    public var intModel : Int?
    
    /// 布尔model
    public var boolModel : Bool?
    
    /// 圆角模型
    public var borderModel : HTableViewBorderModel?
    
    /// 复用标识,特殊情况同一个cell分开复用 (默认 = "cellName" )
    private var _reuserIdentifier = ""
    public var reuserIdentifier: String {
        get {
            if (_reuserIdentifier.count > 0) {
                return _reuserIdentifier
            }
            else {
                return NSStringFromClass(cellClasss)
            }
        }
        set {
            _reuserIdentifier = newValue
        }
    }
    
    // MARK: 回调接口
    /// 回调
    public var didSelectRow : () -> () = {}
    /// 回调index
    public var didSelectRowAtIndex : (_ index: Int) -> () = {_ in}
    /// 回调index
    public var ifCanSelectRowAtIndex : (_ index: Int) -> (Bool) = {_ in return true}
    /// 回调Any
    public var didCallBackWithData : (_ data: AnyObject) -> () = {_ in}
    /// 回调Any + index
    public var didCallBackwithDataAndIndex : (_ data: AnyObject, _ index: Int) -> () = {data, index  in}
    /// 编辑
    public var didEditRowWithActionAtIndex : (_ index: Int) -> () = {_ in}
    
    /// 类方法,便利构造器
    class func separateRow(height: Double, color: UIColor, leftMargin: Double = 0.0, righMargin: Double = 0.0) -> HTableViewRowModel  {
        let row = HTableViewRowModel.init();
        row.rowHeight = height
        row.cellClasss = HBaseSeparateTableViewCell.self
        row.model = ["\(leftMargin)", "\(righMargin)"] as AnyObject
        row.subModel = color
        return row
    }
    
    /// 编辑模式
    private var _editingStyles: NSMutableArray = {
        let styles = NSMutableArray.init()
        return styles
    }()
    var editingStyles : NSMutableArray {
        get{
            return _editingStyles
        }
    }
    
    public func addEditStyle(_ style: HTableViewRowEditingModel) {
        _editingStyles.add(style)
    }
    
}

HTableViewSectionHeaderModel和HTableViewRowEditingModel,主要表达每组的头样式,每个cell的编辑样式

/// section header data model
open class HTableViewSectionHeaderModel {
   
    var headerHeight = 0.0
    
    var headerView :UIView?
}

/// row edit model
open class HTableViewRowEditingModel {
    
    var backgroundColor = UIColor.red
    
    var title = "编辑"
}

HBaseTableViewCell,UITableViewCell的子类,主要接收和使用上面模型类的数据、回调方式

import UIKit

open class HBaseTableViewCell: UITableViewCell {
    
    public var row: HTableViewRowModel?
    
    required public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        self.selectionStyle = .none
        
        self.contentView.addSubview(highLightView)
        highLightView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview()
        }
        
        /// 后面使用用的时候,直接在这里初始化cell即可
        self.cellDidLoad()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    /// called when init
    public func cellDidLoad(){
        
    }
    
    /// called when table view reload data or cell will appear
    func cellWillAppear() {
        
    }
    
    // MARK: touche and high light
    open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        highLightView.backgroundColor = kCOLOR_BGF2
    }
    
    open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        highLightView.backgroundColor = .clear
        self.row?.didSelectRow()
    }
    
    override open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        highLightView.backgroundColor = .clear
    }
    
    lazy var highLightView: UIView = {
        let view = UIView.init()
        return view
    }()
    
    
    // MARK: quick get the model
    /// Height of the cell.
    public var rowHeight :Double {
        get {
            return row?.rowHeight ?? 0
        }
    }
    
    /// Main model.
    public var model :AnyObject {
        get {
            return (row?.model)!
        }
    }
    
    /// Sub model if the vc need more objc to control the cell.
    public var subModel :AnyObject {
        get {
            return (row?.subModel)!
        }
    }
    
    /// String model.
    public var strModel :String {
        get {
            return (row?.strModel) ?? ""
        }
    }
    
    /// Int model.
    public var intModel :Int {
        get {
            return (row?.intModel) ?? 0
        }
    }
    
    /// Bool model.
    public var boolModel :Bool {
        get {
            return (row?.boolModel) ?? false
        }
    }
}

3、使用示例

1)初始化,建议自己封装一个tableView的控制器,以后初始化的代码省略了

class SWBaseTableViewController: SWBaseViewController, HTableViewManagerDataSource {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
        self.view.addSubview(tableView)
        tableView.snp.makeConstraints({ (make) in
            make.edges.equalToSuperview()
        });
    }
    
    
    lazy var tableView: UITableView = {
        let tableView = UITableView.init()
        var topInset = CGFloat(kNaviHeight)
        if is_fullScreen == false {
            topInset = 54
        }
        tableView.contentInset = UIEdgeInsets.init(top: topInset, left: CGFloat(0.0), bottom: CGFloat(kTabHeight), right: CGFloat(0.0))
        return tableView
    }()
    
    lazy var tableManager: HTableViewManager = {
        let tableManager = HTableViewManager.init(tableView: tableView, dataSource: self)
        return tableManager
    }()
}

2)展示不同的内容

    // 代理函数
    func tableViewDataSource(_ tableManager: HTableViewManager) {
        
        // section1
        tableManager.addSection(itemSection())

        // section2、3、4,按需区分不同的卡片
    }
    
    // section
    func itemSection() ->HTableViewSectionModel {
        let section = HTableViewSectionModel.init()
        
        let row = HTableViewRowModel.init()
        // 实际的cell
        row.cellClasss = HUserTableViewCell.self
        // cell 要展示的数据
        let dataList = [1,2,3] // 这里使用你实际要展示的数据,可以传各种模型
        row.model = dataList as AnyObject
        // cell 高度设定
        row.rowHeight = dateList.size * 20

        section.addRow(row)

        // 可以再加其他cell
        // section.addRow(row1)
        // section.addRow(row2)


        return section
    }

3) 事件回调

        
        row.didSelectRow = {[weak self] in
            // 点击整个cell
        }
        row.didSelectRowAtIndex = {[weak self] index in
            // 点击cell的不同元素,根据index处理不同的点击/回调
        }
        
        row.didCallBackWithData = {[weak self] data in
            // 从cell中回调数据给控制器
        }

4)sectionHeader

    /// section header
    var headerView :HTableViewSectionHeaderModel?

5)cell编辑 

let editModel = HTableViewRowEditingModel()
editModel.title = "编辑"
editModel.backgroundColor = .red
row.addEditStyle()

row.didEditRowWithActionAtIndex = { index in
    // 处理编辑事件
}

以上就是封装后的使用示例,可以发看到,卡片化管理使得代码少了很多、模型保存了tableView代理所需要的所有数据。


三、总结

优化的思路很明显————模型控制。大致理解思路后可结合你的业务特点自己封装,优化掉其中不适合你的细节点,在实际的业务中不断打磨,慢慢地变成自己比较熟悉的一个组件。每个公司的产品和UI设计区别都很大,他们的风格直接影响你各个组件的能力。

在这个框架之下,我已经进行约6个app的开发和维护,都是经过打磨和验证的。最后,附上OC的实现方式

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值