Swift - 两个 tableView 间联动功能的实现(左侧分类列表,右侧商品列表)
TableView 与 TableView 之间的联动效果在许多电商 App(比如京东)或者外卖 App(比如美团外卖)上很常见。下面通过样例演示这个功能如何实现。
(2)LeftTableViewCell.swift(左侧表格的自定义单元格)
(3)RightTableViewCell.swift(右侧表格的自定义单元格)
(4)RightTableViewHeader.swift(右侧表格的自定义分区头)
(5)RightTableModel.swift(右侧表格数据模型)
(6)UIColor+.swift(UIColor 扩展)
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+.swift(UIColor 扩展)
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