概述

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

这样做的目的是为了完全复用,支持无限扩展。因为菜单栏是collectionView,所以不怕内存爆掉。VC的控制使用PageViewController,好处是滑动的时候可以懒加载,只有用户浏览的时候才会实例化并缓存起来。网上的其他方案都是一次性把所有VC都实例化,然后使用CollectionView管理,这是不好的,因为有些VC用户可能从来不浏览,没必要实例化。

接下来就详细介绍一下。

实现难点

  1. 菜单栏需要把所选的一栏居中显示

    使用ScrollView,需要手动计算,设置offset,让其被选栏居中,比较麻烦。如果使用CollectionView,CollectionView有一个方法:

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

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

  2. 左右滑动的时候,可以切换所选菜单,且下方横线需要跟着动

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

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

    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

新闻类App顶部菜单栏封装的更多相关文章

  1. 新闻类App使用的组件

    UI SlidingMenu:com.jeremyfeinstein.slidingmenu:滑动菜单 ActionBarSherlock:com.actionbarsherlock:Action B ...

  2. iOS开发之资讯类App常用分类控件的封装与实现(CollectionView+Swift3.0+)

    今天博客中,我们就来实现一下一些常用资讯类App中常用的分类选择的控件的封装.本篇博客中没有使用到什么新的技术点,如果非得说用到了什么新的技术点的话,那么勉强的说,用到了一些iOS9以后UIColle ...

  3. 分享一下怎么开发一款图片视频类App,秒拍和prisma

    第一步,分解短视频App的功能 我们在秒拍官网看到如此描述: [视频拍摄及导入]支持直接拍摄及导入手机本地的视频 [照片电影]照片专属特效,轻松创作照片电影 [MV特效]10余款全新MV特效,让普通视 ...

  4. iOS开发之常用资讯类App的分类展示与编辑的完整案例实现(Swift版)

    上篇博客我们聊了<资讯类App常用分类控件的封装与实现(CollectionView+Swift3.0)>,今天的这篇博客就在上篇博客的基础上做些东西.做一个完整的资讯类App中的分类展示 ...

  5. GNE: 4行代码实现新闻类网站通用爬虫

    GNE(GeneralNewsExtractor)是一个通用新闻网站正文抽取模块,输入一篇新闻网页的 HTML, 输出正文内容.标题.作者.发布时间.正文中的图片地址和正文所在的标签源代码.GNE在提 ...

  6. 三分之一的程序猿之社交类app踩过的那些坑

    三分之一的程序猿之社交类app踩过的那些坑 万众创新,全民创业.哪怕去年陌生人社交不管融资与否都倒闭了不知道多少家,但是依然有很多陌生人社交应用层出不穷的冒出来.各种脑洞大开,让人拍案叫起. 下面我们 ...

  7. 为什么那么多人想开发一元夺宝类app?

    别拿你的无知和愚蠢,来证明主观的判断! 国人对一切事物具有怀疑的本性是好的, 但是若不建立于科学的分析方法, 那就是愚昧! 身边有朋友玩夺宝投入较多,产出较少,于是向我求助.想从数据分析的角度知道到底 ...

  8. 2016年上半年金融类App成绩单,手机银行优势尽显! (转自Analysys易观(ID:enfodesk))

    2016已悄然时过大半,金融各领域经过了开年大战,二季度末尾的6月更是几家欢喜几家愁,其中频繁出现的黑马更是足够让人惊喜.我们基于易观千帆6月移动应用大数据,筛选了百款金融类App为您揭晓TOP100 ...

  9. 移动互联网实战--资源类APP的数据存储处理和优化

    前言: 对于资源类的APP, 其音频/图形占据了APP本身很大的比例. 如何存储和管理这些资源文件, 成了一个颇具挑战性的难点. 移动端的碎片化, 高中低端手机的并存, 需要开发者不光是具备基础的存储 ...

随机推荐

  1. p1184高手之在一起

    P1184 题目背景 高手是可以复活的,这点我们大家都知道. 题目描述 高手列出了一个详尽的日程表,这次他要追求的则是一个心灵纯洁的小萝莉.他和她都是要上课的,但是也会有时间空闲,于是高手决定无时无刻 ...

  2. Yii2 如何输出 sql 语句?

    可以用 $model->find()->createCommand()->getRawSql(); 不能带 all(). find() 它的 返回值类型是 yii\db\Active ...

  3. mysql 替换字符中部分字符,替换使用指定字符

    update table_name set name= replace(name,'我是','是');

  4. CSharp for Jupyter Notebook

    之前说有机会就说下Linux下如何搭建C#版的交互编程,今天写篇文章还债^_^ Win下比较简单,可以自己看官方文档,下面逆天带大家搭建下Linux下的环境(官方方法有问题) 在线预览:https:/ ...

  5. 【模板】多项式乘法(FFT)

    题目描述 给定一个n次多项式F(x),和一个m次多项式G(x). 请求出F(x)和G(x)的卷积. 输入输出格式 输入格式: 第一行2个正整数n,m. 接下来一行n+1个数字,从低到高表示F(x)的系 ...

  6. 洛谷 P1393 P3157 动态逆序对

    嘛,好久没碰CDQ分治了,做道题练练手. 时间倒流——把删数改为加数. 对于每个被删的,我的想法是拆成询问和add,后来发现一个足矣. 我本来准备对每个删的数都求一遍整体逆序对,后来发现无论如何都不可 ...

  7. (转)ZooKeeper的Znode剖析

    ZooKeeper的Znode剖析 https://blog.csdn.net/lihao21/article/details/51810395 根据节点的存活时间,可以对节点划分为持久节点和临时节点 ...

  8. c#中内置委托

    一.Action泛型委托 和之前委托最大区别在于不用定义委托,直接实例化委托(申明就可以),泛型其实就是选择数据类型,进行使得程序更加安全,并且也实现了委托的重载,最多选择16个参数. namespa ...

  9. keepalived+LVS实现网站负载均衡和HA

    如上图所示,102和103是内网nginx服务器,100和101是边界LB,clinet是1,这个实验是为了实现在LB上虚拟出一个VIP,client通过访问该VIP,来动态负载到两台内网nginx服 ...

  10. Linux/Unix系统QA

    Q1:Ext3的三种日志记录方式 1 data=writeback 方式data=writeback方式下,ext3根本不执行任何形式的数据日志记录,提供给您的是和在XFS,JFS和 ReiserFS ...