返回 导航

Swift

hangge.com

Swift - tableView与collectionView联动功能的实现(左侧大分类,右侧小分类)

作者:hangge | 2019-01-15 08:10
  在前文中我演示了如何实现 TableViewTableView 之间的联动效果(点击查看)。而 TableViewCollectionView 之间联动在许多 App 上也很常见。其实它们实现的原理都差不多,下面通过样例进行演示。

1,效果图

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

2,实现原理

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

3,样例代码

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

class ViewController: UIViewController {

    //左侧tableView
    lazy var tableView : UITableView = {
        let tableView = UITableView()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.frame = CGRect(x: 0, y: 0, width: 80,
                                     height: UIScreen.main.bounds.height)
        tableView.rowHeight = 55
        tableView.showsVerticalScrollIndicator = false
        tableView.separatorColor = UIColor.clear
        tableView.register(TableViewCell.self,
                               forCellReuseIdentifier: "tableViewCell")
        return tableView
    }()
    
    //右侧collectionView的布局
    lazy var flowlayout : UICollectionViewFlowLayout = {
        let flowlayout = UICollectionViewFlowLayout()
        flowlayout.scrollDirection = .vertical
        flowlayout.minimumLineSpacing = 2
        flowlayout.minimumInteritemSpacing = 2
        //分组头悬停
        flowlayout.sectionHeadersPinToVisibleBounds = true
        let itemWidth = (UIScreen.main.bounds.width - 80 - 4 - 4) / 3
        flowlayout.itemSize = CGSize(width: itemWidth,
                                     height: itemWidth + 30)
        return flowlayout
    }()
    
    //右侧collectionView
    lazy var collectionView : UICollectionView = {
        let collectionView = UICollectionView(frame: CGRect.init(x: 2 + 80, y: 2 + 64,
                                            width: UIScreen.main.bounds.width - 80 - 4,
                                            height: UIScreen.main.bounds.height - 64 - 4),
                                            collectionViewLayout: self.flowlayout)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.backgroundColor = UIColor.clear
        collectionView.register(CollectionViewCell.self,
                                forCellWithReuseIdentifier: "collectionViewCell")
        collectionView.register(CollectionViewHeader.self,
                    forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
                    withReuseIdentifier: "collectionViewHeader")
        return collectionView
    }()
    
    //左侧tableView数据
    var tableViewData = [String]()
    //右侧collectionView数据
    var collectionViewData = [[CollectionViewModel]]()
    
    //右侧collectionView当前是否正在向下滚动(即true表示手指向上滑动,查看下面内容)
    var collectionViewIsScrollDown = true
    //右侧collectionView垂直偏移量
    var collectionViewLastOffsetY : CGFloat = 0.0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //初始化左侧表格数据
        for i in 1..<15 {
            self.tableViewData.append("分类\(i)")
        }
        
        //初始化右侧表格数据
        for _ in tableViewData {
            var models = [CollectionViewModel]()
            for i in 1..<6 {
                models.append(CollectionViewModel(name: "型号\(i)", picture: "image"))
            }
            self.collectionViewData.append(models)
        }
        
        //将tableView和collectionView添加到页面上
        view.addSubview(tableView)
        view.addSubview(collectionView)
        
        //左侧表格默认选中第一项
        tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: true,
                                scrollPosition: .none)
    }
}

// tableView相关的协议方法
extension ViewController : UITableViewDataSource, UITableViewDelegate {
    //表格分区数
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    //分区下单元格数量
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableViewData.count
    }
    
    //返回自定义单元格
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell",
                                                 for: indexPath) as! TableViewCell
        cell.titleLabel.text = tableViewData[indexPath.row]
        return cell
    }
    
    //单元格选中时调用
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //右侧collection自动滚动到对应的分区
        collectionViewScrollToTop(section: indexPath.row, animated: true)
        
        //左侧tableView将该单元格滚动到顶部
        tableView.scrollToRow(at: IndexPath(row: indexPath.row, section: 0),
                                  at: .top, animated: true)
    }
    
    //将右侧colletionView的指定分区自动滚动到最顶端
    func collectionViewScrollToTop(section: Int, animated: Bool) {
        let headerRect = collectionViewHeaderFrame(section: section)
        let topOfHeader = CGPoint(x: 0, y: headerRect.origin.y
            - collectionView.contentInset.top)
        collectionView.setContentOffset(topOfHeader, animated: animated)
    }
    
    //后获colletionView的指定分区头的高度
    func collectionViewHeaderFrame(section: Int) -> CGRect {
        let indexPath = IndexPath(item: 0, section: section)
        let attributes = collectionView.collectionViewLayout
            .layoutAttributesForSupplementaryView(ofKind:
                UICollectionView.elementKindSectionHeader, at: indexPath)
        guard let frameForFirstCell = attributes?.frame else {
            return .zero
        }
        return frameForFirstCell;
    }
}

// collectionView相关的协议方法
extension ViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
    // 获取分区数
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return tableViewData.count
    }
    
    // 分区下单元格数量
    func collectionView(_ collectionView: UICollectionView,
                        numberOfItemsInSection section: Int) -> Int {
        return collectionViewData[section].count
    }
    
    //返回自定义单元格
    func collectionView(_ collectionView: UICollectionView,
                        cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier:
            "collectionViewCell", for: indexPath) as! CollectionViewCell
        let model = collectionViewData[indexPath.section][indexPath.row]
        cell.setData(model)
        return cell
    }
    
    //分区头尺寸
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: UIScreen.main.bounds.width, height: 30)
    }
    
    //返回自定义分区头
    func collectionView(_ collectionView: UICollectionView,
                        viewForSupplementaryElementOfKind kind: String,
                        at indexPath: IndexPath) -> UICollectionReusableView {
        let view = collectionView.dequeueReusableSupplementaryView(ofKind:
            UICollectionView.elementKindSectionHeader,
            withReuseIdentifier: "collectionViewHeader",
            for: indexPath) as! CollectionViewHeader
        view.titleLabel.text = tableViewData[indexPath.section]
        return view
    }
    
    //分区头即将要显示时调用
    func collectionView(_ collectionView: UICollectionView,
                        willDisplaySupplementaryView view: UICollectionReusableView,
                        forElementKind elementKind: String, at indexPath: IndexPath) {
        //如果是由用户手动滑动屏幕造成的向上滚动,那么左侧表格自动选中该分区对应的分类
        if !collectionViewIsScrollDown
            && (collectionView.isDragging || collectionView.isDecelerating) {
            tableView.selectRow(at: IndexPath(row: indexPath.section, section: 0),
                                    animated: true, scrollPosition: .top)
        }
    }
    
    //分区头即将要消失时调用
    func collectionView(_ collectionView: UICollectionView,
                        didEndDisplayingSupplementaryView view: UICollectionReusableView,
                        forElementOfKind elementKind: String, at indexPath: IndexPath) {
        //如果是由用户手动滑动屏幕造成的向下滚动,那么左侧表格自动选中该分区对应的下一个分区的分类
        if collectionViewIsScrollDown
            && (collectionView.isDragging || collectionView.isDecelerating) {
            tableView.selectRow(at: IndexPath(row: indexPath.section + 1, section: 0),
                                animated: true, scrollPosition: .top)
        }
    }
    
    //视图滚动时触发(主要用于记录当前collectionView是向上还是向下滚动)
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if collectionView == scrollView {
            collectionViewIsScrollDown = collectionViewLastOffsetY
                < scrollView.contentOffset.y
            collectionViewLastOffsetY = scrollView.contentOffset.y
        }
    }
}

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

//左侧表格的自定义单元格
class TableViewCell: 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)CollectionViewCell.swift(右侧网格的自定义单元格)
import UIKit

//右侧collectionView的自定义单元格
class CollectionViewCell: UICollectionViewCell {
    //标题文本标签
    var titleLabel = UILabel()
    //产品图片视图
    var pictureView = UIImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        //初始化标题文本标签
        titleLabel.frame = CGRect.init(x: 2, y: frame.size.width,
                                       width: frame.size.width - 4, height: 20)
        titleLabel.font = UIFont.systemFont(ofSize: 12)
        titleLabel.textAlignment = .center
        contentView.addSubview(titleLabel)
        
        //初始化产品图片视图
        pictureView.frame = CGRect(x: 8, y: 8, width: frame.size.width - 16,
                                   height: frame.size.width - 16)
        pictureView.contentMode = .scaleAspectFit
        contentView.addSubview(pictureView)
    }
    
    //设置数据
    func setData(_ model : CollectionViewModel) {
        titleLabel.text = model.name
        pictureView.image = UIImage(named: model.picture)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

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

//右侧collectionView的自定义分区头
class CollectionViewHeader: UICollectionReusableView {
    
    //分区头文本标签
    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)CollectionViewModel.swift(右侧网格的数据模型)
import UIKit

//右侧collectionView的数据模型(大分类下的小分类)
class CollectionViewModel: NSObject {
    
    //小分类名称
    var name : String
    //小分类图片
    var picture : String
    
    init(name: String, picture: String) {
        self.name = name
        self.picture = picture
    }
}

(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_2259.zip
评论

全部评论(0)

回到顶部