【Swift 联动】:两个 TableView 之间的联动,TableView 与 CollectionView 之间的联动

前言

之前用 Objective-C 写了一篇联动的 demo 和文章,后来有小伙伴私信我有没有 Swfit 语言的,最近趁晚上和周末学习了一下 Swift 3.0 的语法,写了一个 Swift 的 demo。
思路和 Objective-C 版本的联动文章 一样,实现的效果也是一样。先来看下效果图。

联动.gif

正文

一、TableView 与 TableView 之间的联动

下面来说下实现两个 TableView 之间联动的主要思路:
先解析数据装入模型。

// 数据校验
guard let path = Bundle.main.path(forResource: "meituan", ofType: "json") else { return }

guard let data = NSData(contentsOfFile: path) as? Data else { return }

guard let anyObject = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) else { return }

guard let dict = anyObject as? [String : Any] else { return }

guard let datas = dict["data"] as? [String : Any] else { return }

guard let foods = datas["food_spu_tags"] as? [[String : Any]] else { return }

for food in foods {
    
    let model = CategoryModel(dict: food)
    categoryData.append(model)
    
    guard let spus = model.spus else { continue }
    var datas = [FoodModel]()
    for fModel in spus {
        datas.append(fModel)
    }
    foodData.append(datas)
}

定义两个 TableView:LeftTableView 和 RightTableView。

fileprivate lazy var leftTableView : UITableView = {
   let leftTableView = UITableView()
   leftTableView.delegate = self
   leftTableView.dataSource = self
   leftTableView.frame = CGRect(x: 0, y: 0, width: 80, height: ScreenHeight)
   leftTableView.rowHeight = 55
   leftTableView.showsVerticalScrollIndicator = false
   leftTableView.separatorColor = UIColor.clear
   leftTableView.register(LeftTableViewCell.self, forCellReuseIdentifier: kLeftTableViewCell)
   return leftTableView
}()
    
fileprivate lazy var rightTableView : UITableView = {
   let rightTableView = UITableView()
   rightTableView.delegate = self
   rightTableView.dataSource = self
   rightTableView.frame = CGRect(x: 80, y: 64, width: ScreenWidth - 80, height: ScreenHeight - 64)
   rightTableView.rowHeight = 80
   rightTableView.showsVerticalScrollIndicator = false
   rightTableView.register(RightTableViewCell.self, forCellReuseIdentifier: kRightTableViewCell)
   return rightTableView
}()
    
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if leftTableView == tableView {
        let cell = tableView.dequeueReusableCell(withIdentifier: kLeftTableViewCell, for: indexPath) as! LeftTableViewCell
        let model = categoryData[indexPath.row]
        cell.nameLabel.text = model.name
        return cell
    } else {
        let cell = tableView.dequeueReusableCell(withIdentifier: kRightTableViewCell, for: indexPath) as! RightTableViewCell
        let model = foodData[indexPath.section][indexPath.row]
        cell.setDatas(model)
        return cell
    }  
}

先将左边的 TableView 关联右边的 TableView:点击左边的 TableViewCell,右边的 TableView 跳到相应的分区列表头部。

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
   if leftTableView == tableView {
       return nil
   }
   let headerView = TableViewHeaderView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: 20))
   let model = categoryData[section]
   headerView.nameLabel.text = model.name
   return headerView
}

再将右边的 TableView 关联左边的 TableView:标记一下RightTableView 的滚动方向,然后分别在 TableView 分区标题即将展示和展示结束的代理函数里面处理逻辑。

  • 1.在 TableView 分区标题即将展示里面,判断当前的 tableView 是 RightTableView,RightTableView 滑动的方向向上,RightTableView 是用户拖拽而产生滚动的(主要判断RightTableView 是用户拖拽的,还是点击 LeftTableView 滚动的),如果三者都成立,那么 LeftTableView 的选中行就是 RightTableView 的当前 section。
  • 2.在 TableView 分区标题展示结束里面,判断当前的 tableView 是 RightTableView,滑动的方向向下,RightTableView 是用户拖拽而产生滚动的,如果三者都成立,那么 LeftTableView 的选中行就是 RightTableView 的当前 section-1。
// 标记一下 RightTableView 的滚动方向,是向上还是向下
func scrollViewDidScroll(_ scrollView: UIScrollView) {
        
   let tableView = scrollView as! UITableView
   if rightTableView == tableView {
       isScrollDown = lastOffsetY < scrollView.contentOffset.y
       lastOffsetY = scrollView.contentOffset.y
   }
}

// TableView分区标题即将展示
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
   // 当前的tableView是RightTableView,RightTableView滚动的方向向上,RightTableView是用户拖拽而产生滚动的((主要判断RightTableView用户拖拽而滚动的,还是点击LeftTableView而滚动的)
   if (rightTableView == tableView) && !isScrollDown && rightTableView.isDragging {
       selectRow(index: section)
   }
}
    
// TableView分区标题展示结束
func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
   // 当前的tableView是RightTableView,RightTableView滚动的方向向下,RightTableView是用户拖拽而产生滚动的((主要判断RightTableView用户拖拽而滚动的,还是点击LeftTableView而滚动的)
   if (rightTableView == tableView) && isScrollDown && rightTableView.isDragging {
       selectRow(index: section + 1)
   }
}
    
// 当拖动右边TableView的时候,处理左边TableView
private func selectRow(index : Int) {
   leftTableView.selectRow(at: IndexPath(row: index, section: 0), animated: true, scrollPosition: .top)
}

这样就实现了两个 TableView 之间的联动,是不是很简单。

二、TableView 与 CollectionView 之间的联动

TableView 与 CollectionView 之间的联动与两个 TableView 之间的联动逻辑类似。
下面说下实现 TableView 与 CollectionView 之间的联动的主要思路:
还是一样,先解析数据装入模型。

// 数据校验
guard let path = Bundle.main.path(forResource: "liwushuo", ofType: "json") else { return }
        
guard let data = NSData(contentsOfFile: path) as? Data else { return }
   
guard let anyObject = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) else { return }
   
guard let dict = anyObject as? [String : Any] else { return }
   
guard let datas = dict["data"] as? [String : Any] else { return }
   
guard let categories = datas["categories"] as? [[String : Any]] else { return }
   
for category in categories {
  let model = CollectionCategoryModel(dict: category)
  dataSource.append(model)
  
  guard let subcategories = model.subcategories else { continue }
  
  var datas = [SubCategoryModel]()
  for subcategory in subcategories {
      datas.append(subcategory)
  }
  collectionDatas.append(datas)
}

定义一个 TableView,一个 CollectionView。

fileprivate lazy var tableView : UITableView = {
   let tableView = UITableView()
   tableView.delegate = self
   tableView.dataSource = self
   tableView.frame = CGRect(x: 0, y: 0, width: 80, height: ScreenHeight)
   tableView.rowHeight = 55
   tableView.showsVerticalScrollIndicator = false
   tableView.separatorColor = UIColor.clear
   tableView.register(LeftTableViewCell.self, forCellReuseIdentifier: kLeftTableViewCell)
   return tableView
}()
    
fileprivate lazy var flowlayout : LJCollectionViewFlowLayout = {
    let flowlayout = LJCollectionViewFlowLayout()
    flowlayout.scrollDirection = .vertical
    flowlayout.minimumLineSpacing = 2
    flowlayout.minimumInteritemSpacing = 2
    flowlayout.itemSize = CGSize(width: (ScreenWidth - 80 - 4 - 4) / 3, height: (ScreenWidth - 80 - 4 - 4) / 3 + 30)
    flowlayout.headerReferenceSize = CGSize(width: ScreenWidth, height: 30)
    return flowlayout
}()

fileprivate lazy var collectionView : UICollectionView = {
    let collectionView = UICollectionView(frame: CGRect.init(x: 2 + 80, y: 2 + 64, width: ScreenWidth - 80 - 4, height: ScreenHeight - 64 - 4), collectionViewLayout: self.flowlayout)
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.showsVerticalScrollIndicator = false
    collectionView.showsHorizontalScrollIndicator = false
    collectionView.backgroundColor = UIColor.clear
    collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: kCollectionViewCell)
    collectionView.register(CollectionViewHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: kCollectionViewHeaderView)
    return collectionView
}()

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCell(withIdentifier: kLeftTableViewCell, for: indexPath) as! LeftTableViewCell
   let model = dataSource[indexPath.row]
   cell.nameLabel.text = model.name
   return cell
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
   let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kCollectionViewCell, for: indexPath) as! CollectionViewCell
   let model = collectionDatas[indexPath.section][indexPath.row]
   cell.setDatas(model)
   return cell
}

先将 TableView 关联 CollectionView,点击 TableViewCell,右边的 CollectionView 跳到相应的分区列表头部。

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectIndex = indexPath.row
   collectionView.scrollToItem(at: IndexPath(row: 0, section: selectIndex), at: .top, animated: true)
   tableView.scrollToRow(at: IndexPath(row: selectIndex, section: 0), at: .top, animated: true)
}

再将 CollectionView 关联 TableView,标记一下 RightTableView 的滚动方向,然后分别在 CollectionView 分区标题即将展示和展示结束的代理函数里面处理逻辑。

  • 1.在 CollectionView 分区标题即将展示里面,判断 当前 CollectionView 滚动的方向向上, CollectionView 是用户拖拽而产生滚动的(主要是判断 CollectionView 是用户拖拽而滚动的,还是点击 TableView 而滚动的),如果二者都成立,那么 TableView 的选中行就是 CollectionView 的当前 section。
  • 2.在 CollectionView 分区标题展示结束里面,判断当前 CollectionView 滚动的方向向下, CollectionView 是用户拖拽而产生滚动的,如果二者都成立,那么 TableView 的选中行就是 CollectionView 的当前 section-1。
// 标记一下 CollectionView 的滚动方向,是向上还是向下
func scrollViewDidScroll(_ scrollView: UIScrollView) {
   if collectionView == scrollView {
       isScrollDown = lastOffsetY < scrollView.contentOffset.y
       lastOffsetY = scrollView.contentOffset.y
   }
}

// CollectionView 分区标题即将展示
func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
   // 当前 CollectionView 滚动的方向向上,CollectionView 是用户拖拽而产生滚动的(主要是判断 CollectionView 是用户拖拽而滚动的,还是点击 TableView 而滚动的)
   if !isScrollDown && collectionView.isDragging {
       selectRow(index: indexPath.section)
   }
}
    
// CollectionView 分区标题展示结束
func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) {
   // 当前 CollectionView 滚动的方向向下,CollectionView 是用户拖拽而产生滚动的(主要是判断 CollectionView 是用户拖拽而滚动的,还是点击 TableView 而滚动的)
   if isScrollDown && collectionView.isDragging {
       selectRow(index: indexPath.section + 1)
   }
}
    
// 当拖动 CollectionView 的时候,处理 TableView
private func selectRow(index : Int) {
   tableView.selectRow(at: IndexPath(row: index, section: 0), animated: true, scrollPosition: .middle)
}
    

TableView 与 CollectionView 之间的联动就这么实现了,是不是也很简单。

TableView 与 CollectionView 之间的联动

最后

由于笔者水平有限,文中如果有错误的地方,或者有更好的方法,还望大神指出。
附上本文的所有 demo 下载链接,【GitHub - Swift 版】【GitHub - OC 版】,配合 demo 一起看文章,效果会更佳。
如果你看完后觉得对你有所帮助,还望在 GitHub 上点个 star。赠人玫瑰,手有余香。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容