当前位置: > > > Swift - 两个 tableView 间联动功能的实现(左侧分类列表,右侧商品列表)

Swift - 两个 tableView 间联动功能的实现(左侧分类列表,右侧商品列表)

    TableView TableView 之间的联动效果在许多电商 App(比如京东)或者外卖 App(比如美团外卖)上很常见。下面通过样例演示这个功能如何实现。

1,效果图

(1)页面左侧表格显示的是所有的商品分类,右侧表格显示的是所有的商品,并按照类别分组(使用不同的分区显示)
(2)当点击左侧类别时,右侧表格会自动滚动到相应类别分区下。
(3)而用户滑动右侧表格时,左侧也会自动选中当前显示商品分类,且选中项会自动滚动到最上方(如果可以的话)。
       

2,实现原理

(1)左侧 tableView 联动右侧 tableView 比较简单。只要点击时获取对应索引值,然后让右侧 tableView 滚动到相应的分区头即可。
(2)右侧 tableView 联动左侧 tableView 麻烦些。我们需要在右侧 tableView 的分区头显示或消失时,触发左侧 tableView 的选中项改变:
  • 当右侧 tableView 分区头即将要显示时:如果此时是向上滚动,且是由用户滑动屏幕造成的,那么左侧 tableView 自动选中该分区对应的分类。
  • 当右侧 tableView 分区头即将要消失时:如果此时是向下滚动,且是由用户滑动屏幕造成的,那么左侧 tableView 自动选中该分区对应的下一个分区的分类。

3,样例代码

(1)ViewController.swift(主视图控制器)
import UIKit

class ViewController: UIViewController {

    //左侧表格
    lazy var leftTableView : UITableView = {
        let leftTableView = UITableView()
        leftTableView.delegate = self
        leftTableView.dataSource = self
        leftTableView.frame = CGRect(x: 0, y: 0, width: 80,
                                     height: UIScreen.main.bounds.height)
        leftTableView.rowHeight = 55
        leftTableView.showsVerticalScrollIndicator = false
        leftTableView.separatorColor = UIColor.clear
        leftTableView.register(LeftTableViewCell.self,
                               forCellReuseIdentifier: "leftTableViewCell")
        return leftTableView
    }()
    
    //右侧表格
    lazy var rightTableView : UITableView = {
        let rightTableView = UITableView()
        rightTableView.delegate = self
        rightTableView.dataSource = self
        rightTableView.frame = CGRect(x: 80, y: 64,
                                      width: UIScreen.main.bounds.width - 80,
                                      height: UIScreen.main.bounds.height - 64)
        rightTableView.rowHeight = 80
        rightTableView.showsVerticalScrollIndicator = false
        rightTableView.register(RightTableViewCell.self,
                                forCellReuseIdentifier: "rightTableViewCell")
        return rightTableView
    }()
    
    //左侧表格数据
    var leftTableData = [String]()
    //右侧表格数据
    var rightTableData = [[RightTableModel]]()
    
    //右侧表格当前是否正在向下滚动(即true表示手指向上滑动,查看下面内容)
    var rightTableIsScrollDown = true
    //右侧表格垂直偏移量
    var rightTableLastOffsetY : CGFloat = 0.0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //初始化左侧表格数据
        for i in 1..<15 {
            self.leftTableData.append("分类\(i)")
        }
        
        //初始化右侧表格数据
        for leftItem in leftTableData {
            var models = [RightTableModel]()
            for i in 1..<5 {
                models.append(RightTableModel(name: "\(leftItem) - 外卖菜品\(i)",
                    picture: "image", price: Float(i)))
            }
            self.rightTableData.append(models)
        }
        
        //将表格添加到页面上
        view.addSubview(leftTableView)
        view.addSubview(rightTableView)
        
        //左侧表格默认选中第一项
        leftTableView.selectRow(at: IndexPath(row: 0, section: 0), animated: true,
                                scrollPosition: .none)
    }
}

extension ViewController : UITableViewDataSource, UITableViewDelegate {
    //表格分区数
    func numberOfSections(in tableView: UITableView) -> Int {
        if leftTableView == tableView {
            return 1
        } else {
            return leftTableData.count
        }
    }
    
    //分区下单元格数量
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if leftTableView == tableView {
            return leftTableData.count
        } else {
            return rightTableData[section].count
        }
    }
    
    //返回自定义单元格
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
        if leftTableView == tableView {
            let cell = tableView.dequeueReusableCell(withIdentifier: "leftTableViewCell",
                                                     for: indexPath) as! LeftTableViewCell
            cell.titleLabel.text = leftTableData[indexPath.row]
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "rightTableViewCell",
                                                     for: indexPath) as! RightTableViewCell
            let model = rightTableData[indexPath.section][indexPath.row]
            cell.setData(model)
            return cell
        }
    }

    //分区头高度(只有右侧表格有分区头)
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        if leftTableView == tableView {
            return 0
        }
        return 30
    }
    
    //返回自定义分区头(只有右侧表格有分区头)
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        if leftTableView == tableView {
            return nil
        }
        let headerView = RightTableViewHeader(frame: CGRect(x: 0, y: 0,
                                            width: UIScreen.main.bounds.width, height: 30))
        headerView.titleLabel.text = leftTableData[section]
        return headerView
    }
    
    //分区头即将要显示时调用
    func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView,
                   forSection section: Int) {
        //如果是右侧表格,且是是由用户手动滑动屏幕造成的向上滚动
        //那么左侧表格自动选中该分区对应的分类
        if (rightTableView == tableView)
            && !rightTableIsScrollDown
            && (rightTableView.isDragging || rightTableView.isDecelerating) {
            leftTableView.selectRow(at: IndexPath(row: section, section: 0),
                                    animated: true, scrollPosition: .top)
        }
    }
    
    //分区头即将要消失时调用
    func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView,
                   forSection section: Int) {
        //如果是右侧表格,且是是由用户手动滑动屏幕造成的向下滚动
        //那么左侧表格自动选中该分区对应的下一个分区的分类
        if (rightTableView == tableView)
            && rightTableIsScrollDown
            && (rightTableView.isDragging || rightTableView.isDecelerating) {
            leftTableView.selectRow(at: IndexPath(row: section + 1, section: 0),
                                    animated: true, scrollPosition: .top)
        }
    }
    
    //单元格选中时调用
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //点击的是左侧单元格时
        if leftTableView == tableView {
            //右侧表格自动滚动到对应的分区
            rightTableView.scrollToRow(at: IndexPath(row: 0, section: indexPath.row),
                                       at: .top, animated: true)
            //左侧表格将该单元格滚动到顶部
            leftTableView.scrollToRow(at: IndexPath(row: indexPath.row, section: 0),
                                      at: .top, animated: true)
        }
    }

    //表格滚动时触发(主要用于记录当前右侧表格时向上还是向下滚动)
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let tableView = scrollView as! UITableView
        if rightTableView == tableView {
            rightTableIsScrollDown = rightTableLastOffsetY < scrollView.contentOffset.y
            rightTableLastOffsetY = scrollView.contentOffset.y
        }
    }
}

(2)LeftTableViewCell.swift(左侧表格的自定义单元格)
import UIKit

//左侧表格的自定义单元格
class LeftTableViewCell: UITableViewCell {
    
    //标题文本标签
    var titleLabel = UILabel()
    //左侧装饰标签
    var leftTag = UIView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        //选中样式无
        selectionStyle = .none
        
        //初始化标题文本标签
        titleLabel.frame = CGRect(x: 15, y: 0, width: 60, height: 55)
        titleLabel.numberOfLines = 0
        titleLabel.font = UIFont.systemFont(ofSize: 15)
        titleLabel.textColor = UIColor(74, 74, 74)
        titleLabel.highlightedTextColor = UIColor(236, 112, 67)
        contentView.addSubview(titleLabel)
        
        //初始化左侧装饰标签
        leftTag.frame = CGRect(x: 0, y: 20, width: 5, height: 15)
        leftTag.backgroundColor = UIColor(236, 112, 67)
        contentView.addSubview(leftTag)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    //在底部绘制1像素的线条
    override func draw(_ rect: CGRect) {
        //获取绘图上下文
        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }
        //线宽
        let lineWidth = 1 / UIScreen.main.scale
        //线偏移量
        let lineAdjustOffset = 1 / UIScreen.main.scale / 2
        //创建一个矩形,它的所有边都内缩固定的偏移量
        let drawingRect = self.bounds.insetBy(dx: lineAdjustOffset, dy: lineAdjustOffset)
        //创建并设置路径
        let path = CGMutablePath()
        path.move(to: CGPoint(x: 0, y: drawingRect.maxY))
        path.addLine(to: CGPoint(x: self.bounds.width, y: drawingRect.maxY))
        //添加路径到图形上下文
        context.addPath(path)
        //设置笔触颜色
        context.setStrokeColor(UIColor(225, 225, 225).cgColor)
        //设置笔触宽度
        context.setLineWidth(lineWidth)
        //绘制路径
        context.strokePath()
    }
    
    //设置选中样式
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        contentView.backgroundColor = selected ? UIColor(254, 254, 254)
            : UIColor(246, 246, 246)
        isHighlighted = selected
        titleLabel.isHighlighted = selected
        leftTag.isHidden = !selected
    }
}

(3)RightTableViewCell.swift(右侧表格的自定义单元格)
import UIKit

//右侧表格的自定义单元格
class RightTableViewCell: UITableViewCell {
    
    //标题文本标签
    var titleLabel = UILabel()
    //价格文本标签
    var priceLabel = UILabel()
    //产品图片视图
    var pictureView = UIImageView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    
        //初始化标题文本标签
        titleLabel.frame = CGRect(x: 80, y: 10, width: 200, height: 30)
        titleLabel.font = UIFont.systemFont(ofSize: 14)
        contentView.addSubview(titleLabel)
        
        //初始化价格文本标签
        priceLabel.frame = CGRect(x: 80, y: 42, width: 200, height: 30)
        priceLabel.font = UIFont.systemFont(ofSize: 14)
        priceLabel.textColor = UIColor(232, 91, 77)
        contentView.addSubview(priceLabel)
        
        //初始化产品图片视图
        pictureView.frame = CGRect(x: 15, y: 15, width: 50, height: 50)
        contentView.addSubview(pictureView)
    }
    
    //设置数据
    func setData(_ model : RightTableModel) {
        titleLabel.text = model.name
        priceLabel.text = "¥\(model.price)"
        pictureView.image = UIImage(named: model.picture)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

(4)RightTableViewHeader.swift(右侧表格的自定义分区头)
import UIKit

//右侧表格的自定义分区头
class RightTableViewHeader: UIView {
    
    //分区头文本标签
    var titleLabel = UILabel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        //设置分区头背景色
        backgroundColor = UIColor.white
        
        //初始化分区头文本标签
        titleLabel.frame = CGRect(x: 15, y: 0, width: 200, height: 30)
        titleLabel.font = UIFont.systemFont(ofSize: 13)
        addSubview(titleLabel)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

(5)RightTableModel.swift(右侧表格数据模型)
import UIKit

//右侧表格数据模型(分类下的商品)
class RightTableModel: NSObject {
    
    //商品名称
    var name : String
    //商品图片
    var picture : String
    //商品价格
    var price : Float
    
    init(name: String, picture: String, price: Float) {
        self.name = name
        self.picture = picture
        self.price = price
    }
}

(6)UIColor+.swiftUIColor 扩展)
import UIKit

//UIColor扩展
extension UIColor {
    
    //使用rgb方式生成自定义颜色
    convenience init(_ r : CGFloat, _ g : CGFloat, _ b : CGFloat) {
        let red = r / 255.0
        let green = g / 255.0
        let blue = b / 255.0
        self.init(red: red, green: green, blue: blue, alpha: 1)
    }
    
    //使用rgba方式生成自定义颜色
    convenience init(_ r : CGFloat, _ g : CGFloat, _ b : CGFloat, _ a : CGFloat) {
        let red = r / 255.0
        let green = g / 255.0
        let blue = b / 255.0
        self.init(red: red, green: green, blue: blue, alpha: a)
    }
}
源码下载hangge_2258.zip
评论0