新闻类App顶部菜单栏封装

概述

最近有一个需求,相似今日头条顶部的菜单栏。惟一区别是须要带可移动的下划线。网上查找资料,发现解决方案大部分是用UIScrollView实现。下方VC控制用UICollectionView。这样能够解决问题,可是不完美,当标签不少的时候,这时候的UIScrollView上会有大量写死的Button,没有达到复用的目的。因此本身封装了一个空间。菜单栏使用UICollectionView,VC控制使用PageViewController。git

这样作的目的是为了彻底复用,支持无限扩展。由于菜单栏是collectionView,因此不怕内存爆掉。VC的控制使用PageViewController,好处是滑动的时候能够懒加载,只有用户浏览的时候才会实例化并缓存起来。网上的其余方案都是一次性把全部VC都实例化,而后使用CollectionView管理,这是很差的,由于有些VC用户可能历来不浏览,不必实例化。github

接下来就详细介绍一下。swift

实现难点

  1. 菜单栏须要把所选的一栏居中显示数组

    使用ScrollView,须要手动计算,设置offset,让其被选栏居中,比较麻烦。若是使用CollectionView,CollectionView有一个方法: 缓存

    open func scrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool)

    只要将scrollPosition设置为.centeredHorizontally,便可实现该功能app

  2. 左右滑动的时候,能够切换所选菜单,且下方横线须要跟着动ide

    使用ScrollView的话就比较方便了,计算滑动距离和屏幕宽的比例,让下划线跟着滑便可。可是使用CollectionView的话,滑完以后会自动居中显示被选菜单。位置就会出错。解决方案就是让下划线跟着被选菜单cell的位置。ui

    在collectionView中,滑动cell的时候其实只是offset在变,cell的frame实际上是不变的,collectionView其实也是个ScrollView,cell是加在scrollView的contentView上的。在这里卡壳了很久。解决方案是,将cell的坐标转化到collectionView上,而后让下划线的中心点和cell在collectionView上中心点保持一致blog

     
    if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) {
                lineView.center.x = currentCell.convert(CGPoint(x: currentCell.bounds.width / 2.0, y: 0), to: scrollView).x
            }
  3. PageViewController没有ScrollView的Delegate,滑动的时候,不知道滑动的状况。内存

    可使用一个暗黑技巧:

     
    for subview in pageViewController.view.subviews {
    	            if let scrollView = subview as? UIScrollView {
    	                scrollView.delegate = self
    	            }
    	        }
    

     

    而后再scrollViewDidScroll方法中操做

     
    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
            if isForbideScroll { return}
            
            var progress:CGFloat = 0
            var nextIndex = 0
            let screenWidth = UIScreen.main.bounds.width
            let count = items.count
            
            //判断是左移仍是右移
            if UIScreen.main.bounds.width > scrollView.contentOffset.x{    //右移动
                nextIndex = currentIndex - 1
                if nextIndex < 0 {
                    nextIndex = 0
                }
                //计算progress
                progress = (screenWidth - scrollView.contentOffset.x)/screenWidth
            }
            if UIScreen.main.bounds.width < scrollView.contentOffset.x{    //左移
                nextIndex = currentIndex + 1
                if nextIndex > count - 1 {
                    nextIndex = count - 1
                }
                progress = (scrollView.contentOffset.x - screenWidth)/screenWidth
            }
            if progress != 0.0 {
                topBar.pageViewScroll(nextIndex: nextIndex, progress: progress)
            }
        }
    

     

如何使用

  1. 风格控制类SegmentTopBarStyle
  2. 数据源[SegmentItem]
  3. 自定义VC必须实现ChildViewControllerProtocol协议,协议中初始化方法能够按需修改,增长参数。初始化方法修改后记得在ScrollPageView中修改自定义VC的初始化。
  4. ScrollPageView中使用了SegmentTopBarView,因此你也能够单独使用SegmentTopBarView 
override func viewDidLoad() {
        super.viewDidLoad()
        
        //风格控制
        let style = SegmentTopBarStyle()
        style.bottomLineColor = UIColor.red
        style.showExtraButton = false
        
        //datasource
        let items = dataSource()
        
        //ScrollPageView
        let subiView = ScrollPageView(frame: view.bounds,
                                      items: items,
                                      style: style,
                                      parentVC: self,
                                      customClassType: ChildViewController.self)
        view.addSubview(subiView)
    }

    func dataSource() -> [SegmentItem] {
        // 讲数据转化为SegmentItem的数组
        var arr = [SegmentItem]()
        for i in 0...10 {
            let item = SegmentItem(title: "title\(i)", cid: "\(i)")
            arr.append(item)
        }
        return arr
    }

源码

demo基于swift4.0。代码很少,稍微阅读下就能看懂。你也许会有更多的个性化的定制,能够在这个结构上随意改,拿走不谢

demo: https://github.com/wangdachui/Segment

相关文章
相关标签/搜索