实现下图的效果git
header 上面有一个 scroll viewgithub
图中可视区域内,有 6 个app
能够更新下面 cell 的内容ide
布局精华布局
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 } }
主要是状态的管理,动画
旧状态抹去,spa
新状态呈现debug
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 } } }