Swift - 两个 tableView 间联动功能的实现(左侧分类列表,右侧商品列表)
作者:hangge | 2019-01-11 08:10
TableView 与 TableView 之间的联动效果在许多电商 App(比如京东)或者外卖 App(比如美团外卖)上很常见。下面通过样例演示这个功能如何实现。

(2)LeftTableViewCell.swift(左侧表格的自定义单元格)
(3)RightTableViewCell.swift(右侧表格的自定义单元格)
(4)RightTableViewHeader.swift(右侧表格的自定义分区头)
(5)RightTableModel.swift(右侧表格数据模型)
(6)UIColor+.swift(UIColor 扩展)
hangge_2258.zip
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)
}
}
源码下载:
全部评论(0)