实现下图的成果

档次关系为:

  • Collection View 有一个 header,

header 下面有一个 scroll view

  • Collection View 还有很多 cell

图中可视区域内,有 6 个

成果是,点击 scroll view 下面的 item,

能够更新上面 cell 的内容

第一局部,自定义布局 Collection View layout

布局精髓

class HanGridLayout: UICollectionViewLayout {  override public func prepare() {     guard let collectionView = collectionView else {         return     }         prepareCache()     contentHeight = 0     // 配置 header 的地位,     let headerH = layout.headSize.height     let headerIP = IndexPath(item: 0, section: 0)     let headerAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: CommonComponent.header.kind, with: headerIP)      headerAttributes.frame = CGRect(x: 0, y: contentHeight, width: UI.std.width, height: headerH)      var cellX: CGFloat = layout.contentEdge.left              cache[.header]?[headerIP] = headerAttributes      contentHeight += (headerH + layout.contentEdge.top)            let count = collectionView.numberOfItems(inSection: 0)        // 配置 cell 的地位            for item in 0 ..< count {                  let cellIndexPath = IndexPath(item: item, section: 0)                  let attributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)                                attributes.frame = CGRect( x: cellX, y: contentHeight, width: layout.itemSize.width, height: layout.itemSize.height )                  // 次要是,这里有一个换行                contentHeight += layout.exceed(origin: &cellX, limit: collectionViewWidth)                  cache[.cell]?[cellIndexPath] = attributes            }            // 这里有一个兼顾显示            // 对于最初一行的格子,内容的出现            if count % 2 == 1{                contentHeight += layout.itemSize.height            }       contentHeight += layout.contentEdge.bottom  }}

第 2 局部,header 下面的滚动条

次要是状态的治理,

旧状态抹去,

新状态出现

class HanLanTopV: UICollectionReusableView {    @IBOutlet weak var midView: UIView!    // 文本转 UILabel    var list = [String]()    // 滚动视图    lazy var scroll = UIScrollView()    var names = [UILabel]()        var bags = [Disposable]()        //   动画的,底部的那条线    lazy var line: UIView = // ...        override func awakeFromNib(){        super.awakeFromNib()        //  初始化配置        if scroll.superview == nil{            midView.addSubs([scroll])        }        if line.superview == nil{            scroll.addSubs([line])        }        scroll.snp.makeConstraints { (m) in            m.edges.equalToSuperview()        }    }            func config(package press: String, name n: String, config src: String?, list info: SubNameInfo, selected sIdx: Int){        // 状态还原        bags.forEach {            $0.dispose()        }        names.forEach {            $0.removeFromSuperview()        }        bags.removeAll()        names.removeAll()        // ...        // 简略的显示配置                // 每次刷新进去,        // 底部的标签,都是动静生成        let temps = info.names.map { (str) -> UILabel in            let l = UILabel()            l.text = str            l.isUserInteractionEnabled = true            l.textAlignment = .center            l.textColor = UIColor(rgb: 0x404248)            l.font = UIFont.regular(ofSize: 14)            return l        }        names.append(contentsOf: temps)        scroll.addSubs(names)                for i in 0..<info.cnt{            // names[i].layer.debug()            let tagGesture = UITapGestureRecognizer()            names[i].addGestureRecognizer(tagGesture)            let bag = tagGesture.rx.event.subscribe { (t) in                self.delegate?.choose(idx: i)            }            bags.append(bag)            names[i].snp.makeConstraints { (m) in                m.height.centerY.equalToSuperview()                if i == 0{                    m.leading.equalToSuperview().offset(16)                }                else{                    m.leading.equalTo(names[i - 1].snp.trailing).offset(28)                }                if i == info.cnt - 1{                    m.trailing.equalToSuperview().offset(16.neg)                }            }        }        names[sIdx].textColor = UIColor(rgb: 0x0080FF)        names[sIdx].font = UIFont.semibold(ofSize: 14)        midView.layoutIfNeeded()        // 点击滚动成果,放在这里        // mark & config        animate(idx: sIdx)    }            func animate(idx index: Int){        UIView.animate(withDuration: 0.3) {            let v = self.names[index]            let f = v.frame            self.line.frame = CGRect(x: f.origin.x, y: self.scroll.frame.maxY - 1, width: f.size.width, height: 1)            self.midView.layoutIfNeeded()        } completion: { (_) in            self.line.isHidden = false        }            }    }

github repo