CollectionView section - QDDCoder/LZLearniOS GitHub Wiki

CollectionView-section的使用

使用解析

  • 创建UICollectionView

private lazy var layout = UICollectionViewFlowLayout()
private lazy var collectionView = UICollectionView(frame: CGRect(x: 10*PionWidth, y: NAVBAR_HEIGHT, width: ScreenW-20*PionWidth, height: ScreenH-NAVBAR_HEIGHT),collectionViewLayout: layout).then {
    $0.backgroundColor = .white
    $0.register(LZRXCollectionCell.self, forCellWithReuseIdentifier: "LZRXCollectionCell")
    $0.register(RXSwiftCollectionHeadView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "headerViewId")
    //高度代理
    $0.delegate=self
    
    //拖动使用
    $0.dropDelegate=self
    $0.dragDelegate=self
    //开启可拖动
    $0.dragInteractionEnabled = true
    //弹簧加载是一种导航和激活控件的方式,在整个系统中,当处于 dragSession 的时候,只要悬浮在cell上面,就会高亮,然后就会激活
    $0.isSpringLoaded=true
    //重排序节奏)可以调节集合视图重排序的响应性。 是 CollectionView 独有的属性(相对于UITableView),因为 其独有的二维网格的布局,因此在重新排序的过程中有时候会发生元素回流了,有时候只是移动到别的位置,不想要这样的效果,就可以修改这个属性改变其相应性
    $0.reorderingCadence = .slow
    
    
    //隐藏纵向滑动线
    $0.showsVerticalScrollIndicator = false
    
    //系统自动调整scrollView.contentInset保证滚动视图不被tabbar,navigationbar遮挡
    $0.contentInsetAdjustmentBehavior = .scrollableAxes
    
    // 点击事件
    $0.rx.itemSelected.map {[weak self] (indexPath) in
        return (indexPath,self!.dataSource[indexPath])
    }.subscribe(onNext:{(index,model) in
        ToastView.instance.showToast(content: "点击了===>>>\(index.section)===>>>\(model.name)")
    }).disposed(by: disposeBag)
    

    self.view.addSubview($0)
}
    
  • RxDataSources 数据中间件

//设置数据绑定的中间件
private var dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel<String,HomeCategoryModel>> { (_, collectionView, indexPath, item) -> UICollectionViewCell in
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LZRXCollectionCell", for: indexPath) as! LZRXCollectionCell
    cell.textInfo.text=item.name
    cell.backgroundColor = .randomColor
    cell.dragStateDidChange(.lifting)
    return cell
}.then {
    // 设置sectionHeader
    $0.configureSupplementaryView={(model, collection, info, indexPath) -> UICollectionReusableView in
        let header = collection.dequeueReusableSupplementaryView(ofKind: info, withReuseIdentifier: "headerViewId", for: indexPath) as! RXSwiftCollectionHeadView
        header.titleLabel.text=model.sectionModels[indexPath.section].model
        header.backgroundColor = .randomColor
        return header
    }
    
    //设置可移动
    $0.canMoveItemAtIndexPath={(dataSourceIn,indexPath) -> Bool in
        return true
    }
}
  • 创造数据

private var response:BehaviorSubject<[SectionModel<String, HomeCategoryModel>]>?
private var dataModel=[SectionModel(model: "我的频道", items:
    [
        HomeCategoryModel(withName: "关注", withJump: UIViewController.self),
        HomeCategoryModel(withName: "推荐", withJump: UIViewController.self),
        HomeCategoryModel(withName: "视频", withJump: UIViewController.self),
        HomeCategoryModel(withName: "热点", withJump: UIViewController.self),
        HomeCategoryModel(withName: "北京", withJump: UIViewController.self),
        HomeCategoryModel(withName: "新时代", withJump: UIViewController.self),
        HomeCategoryModel(withName: "图片", withJump: UIViewController.self),
        HomeCategoryModel(withName: "头条号", withJump: UIViewController.self),
        HomeCategoryModel(withName: "娱乐", withJump: UIViewController.self),
        HomeCategoryModel(withName: "问答", withJump: UIViewController.self),
        HomeCategoryModel(withName: "体育", withJump: UIViewController.self),
        HomeCategoryModel(withName: "科技", withJump: UIViewController.self),
        HomeCategoryModel(withName: "懂车帝", withJump: UIViewController.self),
        HomeCategoryModel(withName: "财经", withJump: UIViewController.self),
        HomeCategoryModel(withName: "军事", withJump: UIViewController.self),
        HomeCategoryModel(withName: "国际", withJump: UIViewController.self),
    ]),
    SectionModel(model: "频道推荐", items:
    [
        HomeCategoryModel(withName: "健康", withJump: UIViewController.self),
        HomeCategoryModel(withName: "冬奥", withJump: UIViewController.self),
        HomeCategoryModel(withName: "特产", withJump: UIViewController.self),
        HomeCategoryModel(withName: "房产", withJump: UIViewController.self),
        HomeCategoryModel(withName: "小说", withJump: UIViewController.self),
        HomeCategoryModel(withName: "时尚", withJump: UIViewController.self),
        HomeCategoryModel(withName: "历史", withJump: UIViewController.self),
        HomeCategoryModel(withName: "育儿", withJump: UIViewController.self),
        HomeCategoryModel(withName: "直播", withJump: UIViewController.self),
        HomeCategoryModel(withName: "搞笑", withJump: UIViewController.self),
        HomeCategoryModel(withName: "数码", withJump: UIViewController.self),
        HomeCategoryModel(withName: "美食", withJump: UIViewController.self),
        HomeCategoryModel(withName: "养生", withJump: UIViewController.self),
        HomeCategoryModel(withName: "电影", withJump: UIViewController.self),
        HomeCategoryModel(withName: "手机", withJump: UIViewController.self),
        HomeCategoryModel(withName: "旅游", withJump: UIViewController.self),
        HomeCategoryModel(withName: "宠物", withJump: UIViewController.self),
        HomeCategoryModel(withName: "情感", withJump: UIViewController.self),
    ]
)]
  • UICollectionView的代理

extension LZRXCollectionViewSectionVC: UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
    
    // 设置Cell的大小
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = dataModel[indexPath.section].items[indexPath.row].name.ga_widthForComment(fontSize: 14, height: 20*PionHeight)
        return CGSize(width: width+20*PionWidth, height: 30*PionHeight)
    }
    
    //竖向滚动表示:行cell之间的间隔
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 10*PionWidth
    }
    
    //竖向滚动表示:行cell之间的列间距 注意:和Cell设置大小时的冲突,一般使用设置Cell 大小就行了。特殊应用场景,在cell自适应大小的时候使用
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 10*PionWidth
    }
    
    //foot的大小
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        return CGSize(width:0 , height: 0*PionHeight)
    }
    
    //header的大小
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: ScreenW-20*PionWidth, height: 40*PionHeight)
    }
}
  • 拖动的代理-DragDelegate代理

extension LZRXCollectionViewSectionVC:UICollectionViewDragDelegate{
    //可拖动的item集合
    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard indexPath.section != 1 else {
            return []
        }
        let item = self.dataModel[indexPath.section].items[indexPath.row].name
        let itemProvider = NSItemProvider(object: item as NSString)
        let dragItem = UIDragItem(itemProvider: itemProvider)
        dragItem.localObject = item
        //开始拖动 的indexPath
        dragingIndexPath = indexPath
        return [dragItem]
    }
}
  • 拖动的代理-DropDelegate代理

extension LZRXCollectionViewSectionVC:UICollectionViewDropDelegate
{
    //拖动的数据交换 通过coordinator我们可以获取到操作类型,是move还是copy。
    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
        // 交换目标indexPath
        guard let destinationIndexPath = coordinator.destinationIndexPath else {
            return
        }
        switch coordinator.proposal.operation {
        case .move:
            let items = coordinator.items
            // 交换的发起item和indexPath
            if let item = items.first, let sourceIndexPath = item.sourceIndexPath {
                //执行批量更新
                collectionView.performBatchUpdates({
                    //move 操作后,需要把之前的位置删除掉,在新的位置进行插入
                    let tempItemp = self.dataModel[destinationIndexPath.section].items[sourceIndexPath.row]
                    self.dataModel[destinationIndexPath.section].items.remove(at: sourceIndexPath.row)
                    self.dataModel[destinationIndexPath.section].items.insert(tempItemp, at: destinationIndexPath.row)
                    collectionView.deleteItems(at: [sourceIndexPath])
                    collectionView.insertItems(at: [destinationIndexPath])
                    response?.onNext(dataModel)
                })
                //将项目动画化到视图层次结构中的任意位置
                coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
            }
            break
        default:
            return
        }
    }
    
    //设置拖动样式
    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
        //由于“我的频道”和“频道推荐”是禁止互相滑动的,所以,拖拽的起始dragingIndexPath和目标destinationIndexPath的section不一样,就表示跨区了,设置其为forbidden。
        if dragingIndexPath?.section != destinationIndexPath?.section {
            return UICollectionViewDropProposal(operation: .forbidden)
        }else{
            if session.localDragSession != nil {
                if collectionView.hasActiveDrag {
                    return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
                } else {
                    return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
                }
            } else {
                return UICollectionViewDropProposal(operation: .forbidden)
            }
        }
    }
}

注意

  • 另一种,通过长按手势来实现

    //添加手势
    let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(reorderCollectionView(longPressGesture:)))
    collectionView.addGestureRecognizer(longPressGesture)
    
    
    //手势处理
    @objc func reorderCollectionView(longPressGesture:UILongPressGestureRecognizer)  {
        switch longPressGesture.state {
        case .began:
            //要开始交互式移动item
            let piont = longPressGesture.location(in: self.collectionView)
            let selectIndexPath = collectionView.indexPathForItem(at: piont)
            if selectIndexPath != nil{
                self.collectionView.beginInteractiveMovementForItem(at: selectIndexPath!)
            }
            break
        case .changed:
            //当手势识别器跟踪到手势变化时
            let piont = longPressGesture.location(in: self.collectionView)
            collectionView.updateInteractiveMovementTargetPosition(piont)
            break
        case .ended:
            //手势结束
            collectionView.endInteractiveMovement()
            break
            
        default:
            collectionView.cancelInteractiveMovement()
            break
        }
    }
    • 要开始交互式移动item,CollectionView调用beginInteractiveMovementForItemAtIndexPath:方法
    • 当手势识别器跟踪到手势变化时,集合视图调用updateInteractiveMovementTargetPosition:方法报告最新触摸位置
    • 当手势结束时,UICollectionView调用endInteractiveMovement方法结束交互并更新视图;
    • 当手势中途取消或识别失败,UICollectionView调用cancelInteractiveMovement方法结束交互

延伸

  • 手势控制

    • UITapGestureRecognizer:点击手势识别器,手势可以是一次点击或多次点击,可以是一个手指也可以是多个手指。点击手势是最为常用的手势。一次性手势
      let tapGesture = UITapGestureRecognizer()
      tapGesture.rx.event.subscribe(onNext: {(tap) in
          print("点击了")
      }).disposed(by: disposeBag)
      self.view.addGestureRecognizer(tapGesture)
    • UISwipeGestureRecognizer:滑动手势识别器,滑动方向可以是上下左右任一方向。滑动手势的典型示例是查看照片的应用程序,我们使用手指从一张照片滑动到另一张照片。一次性手势
      let swapGesture = UISwipeGestureRecognizer()
      swapGesture.rx.event.subscribe(onNext: {(swap) in
          //Right = 0 ,Left =1,Up =2,Down =3
          print("方向=>\(swap.direction)")
          print("滑动的手指数=>\(swap.numberOfTouchesRequired)")
          print("响应位置=>\(swap.location(in: swap.view))")
          print("触摸点数量=>\(swap.numberOfTouches)")
          //.begin: 开始,.possible:可用,changed:改变,ended:结束,cancelled:取消,failed:失败
          print("状态=>\(swap.state)")
      }).disposed(by: disposeBag)
      
      self.view.addGestureRecognizer(swapGesture)
    • UIPanGestureRecognizer:平移手势识别器,也称为拖动手势识别器。当用户平移视图时,必须保持一个或多个手指始终按压在视图上。
      let panGesture = UIPanGestureRecognizer()
      panGesture.rx.event.subscribe(onNext: {(pan) in
              print("状态==>\(pan.state)")
              if pan.state == .ended{
                  let speed = pan.velocity(in: pan.view)
                  print("滑动速度:\(speed)")
              }
          }).disposed(by: disposeBag)
          self.view.addGestureRecognizer(panGesture)
    • UIPinchGestureRecognizer:捏合手势识别器,需要两个手指同时触摸视图。当两个手指靠近时,视图缩小;当两根手指远离时,视图放大。
      //创建图片
      private lazy var imageViewSize = CGRect(x: 10*PionWidth, y: NAVBAR_HEIGHT, width: ScreenW-20*PionWidth, height: ScreenH-NAVBAR_HEIGHT)
      private var center:CGPoint?
      private lazy var imageView = UIImageView(frame: imageViewSize).then {
          $0.kf.setImage(with: URL(string: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F11645791963%2F641.jpg&refer=http%3A%2F%2Finews.gtimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611726545&t=20bba1377f9458ff76cef652d805ed62"))
          $0.isUserInteractionEnabled = true
          self.center = $0.center
          self.view.addSubview($0)
          self.view.sendSubviewToBack($0)
      }
      
      //缩放手势
      let pichGesture = UIPinchGestureRecognizer()
      imageView.addGestureRecognizer(pichGesture)
      pichGesture.rx.event.subscribe(onNext: {[weak self](pinch) in
          print(pinch.scale)
          self?.imageViewSize.size.width=pinch.scale*(ScreenW-20*PionWidth)
          self?.imageViewSize.size.height=pinch.scale*(ScreenH-NAVBAR_HEIGHT)
          self?.imageView.frame = self!.imageViewSize
          self?.imageView.center = (self?.center)!
      }).disposed(by: disposeBag)
    • UIRotationGestureRecognizer:旋转手势识别器,需要两个手指同时触摸视图。当用户手指彼此相对做圆周运动时,对应视图会以相同的方向和速度旋转。
      private lazy var imageViewSize = CGRect(x: 10*PionWidth, y: NAVBAR_HEIGHT, width: ScreenW-20*PionWidth, height: ScreenH-NAVBAR_HEIGHT)
      private lazy var imageView = UIImageView(frame: imageViewSize).then {
          $0.kf.setImage(with: URL(string: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F11645791963%2F641.jpg&refer=http%3A%2F%2Finews.gtimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611726545&t=20bba1377f9458ff76cef652d805ed62"))
          $0.isUserInteractionEnabled = true
          self.view.addSubview($0)
          self.view.sendSubviewToBack($0)
      }
      
      let rotationGesture = UIRotationGestureRecognizer()
      imageView.addGestureRecognizer(rotationGesture)
      rotationGesture.rx.event.subscribe(onNext: {[weak self](ratation) in
          print("旋转速度==>>\(ratation.velocity)")
          print("旋转角度==>>\(ratation.rotation)")
          ratation.view?.transform = (ratation.view?.transform.rotated(by: ratation.rotation))!
          //将上次的弧度置为1
          ratation.rotation=0
      }).disposed(by: disposeBag)
    • UILongPressGestureRecognizer:长按手势识别器,想要成功触发长按手势,需使用一个或多个手指在视图上按压不小于minimumPressDuration设定的时长,默认0.5秒,并且长按时手指移动的距离要小于allowableMovement设定的距离,默认距离10points。
      let longPressGesture = UILongPressGestureRecognizer()
      //设置长按时长
      longPressGesture.minimumPressDuration = 0.5
      //设置长按手指数
      longPressGesture.numberOfTouchesRequired=1
      longPressGesture.rx.event.subscribe(onNext: {[weak self](longPress) in
          if longPressGesture.state == .began{
              //长按事件开始
              //可以获取我们在哪个cell上长按 tableview collectionView
              //let piont = longPressGesture.location(in: longPress.view)
              let piont = longPress.location(in: longPress.view)
              let longPressIndexPath = self?.collectionView.indexPathForItem(at: piont)
          }
      }).disposed(by: disposeBag)
      collectionView.isUserInteractionEnabled=true
      collectionView.addGestureRecognizer(longPressGesture)
    • UIScreenEdgePanGestureRecognizer:屏幕边缘轻扫手势识别器,继承自UIPanGestureRecognizer,前面六个手势识别器均继承自UIGestureRecognizer。屏幕边缘轻扫手势识别器只能识别在屏幕边缘附近开始的手势。与滑动手势很像,不同之处在于前者必须从屏幕边缘开始。可以使用屏幕边缘手势来启动视图控制器转换。
      let screenGesture = UIScreenEdgePanGestureRecognizer().then {
          $0.edges = .right
          $0.rx.event.subscribe(onNext: {[weak self](screen) in
              if screen.state == .began{
              }else if screen.state == .changed{
              }else if screen.state == .ended{
                  self?.lz_popViewController(animated: true)
              }
          }).disposed(by: disposeBag)
      }
      self.view.addGestureRecognizer(screenGesture)

      屏幕边缘滑动手势作为一个比较冷门的手势.其实用到的地方也没想象中的那么少. UIScreenEdgePanGestureRecognizer是UIPanGestureRecognizer的子类.

      属性 默认值 说明
      edges UIRectEdgeNone 这个属性是边缘拖动手势支持的拖动的位置,默认是没有,不给这个属性赋值的话这个手势是没有意义的

      属性值

      枚举值 说明
      UIRectEdgeNone 设置为这个值的时候这个手势其实就"失效"了
      UIRectEdgeTop 识别顶部边缘拖动手势
      UIRectEdgeLeft 识别左边边缘拖动手势
      UIRectEdgeBottom 识别底部边缘拖动手势
      UIRectEdgeRight 识别右边边缘拖动手势
      UIRectEdgeAll 识别所有方向的边缘拖动手势
⚠️ **GitHub.com Fallback** ⚠️