前言

前段时间公司做一个新闻类的项目,需要支持频道编辑,缓存等功能,界面效果逻辑就按照最新版的网易新闻来,网上没找到类似的轮子,二话不说直接开撸,为了做到和网易效果一模一样还是遇到不少坑和细节,这在此分享出来,自己做个记录,大家觉得有用的话也可以参考。支持手动集成或者cocoapods集成。

项目地址

github.com/yd2008/YDCh…

最终效果

其实基本就和网易一毛一样了啦,只是为了更加直观还是贴出两张图片

调起方式

因为要弹出一个占据全屏的控件,7.0之前可能是加在window上,但是后面苹果不建议这么做,所以还是直接present一个控制器出来是最优的选择。

public class YDChannelSelector: UIViewController
复制代码

创建

非常简单,遵守数据源协议和代理协议


class ViewController: UIViewController, YDChannelSelectorDataSource, YDChannelSelectorDelegate// 频道选择控制器private lazy var channelSelector: YDChannelSelector = {let sv = YDChannelSelector()sv.dataSource = selfsv.delegate = self// 是否支持本地缓存用户功能 默认开启// sv.isCacheLastest = falsereturn sv}()
复制代码

基于接口傻瓜的原则,呼出窗口最简单的方法就是系统自带的present方法就ok。

present(channelSelector, animated: true, completion: nil)
复制代码

传递数据

作为一个频道选择器,它需要知道哪些关键信息呢?

  • 频道名字
  • 频道是否是固定栏目
  • 频道自己的原始数据

基于以上需求,我设计了频道结构体

public struct SelectorItem {/// 频道名称public var channelTitle: String!/// 是否是固定栏目public var isFixation: Bool!/// 频道对应初始字典或模型public var rawData: Any?public init(channelTitle: String, isFixation: Bool = false, rawData: Any?) {self.channelTitle = channelTitleself.isFixation = isFixationself.rawData = rawData}
}
复制代码

数据源代理方法和tableView一致,上手简单容易

public protocol YDChannelSelectorDataSource: class {func numberOfSections(in selector: YDChannelSelector) -> Intfunc selector(_ selector: YDChannelSelector, numberOfItemsInSection section: Int) -> Intfunc selector(_ selector: YDChannelSelector, itemAt indexPath: IndexPath) -> SelectorItem
}
复制代码

代理

用户做了各种操作后如何通知控制器当前状态

public protocol YDChannelSelectorDelegate: class {/// 数据源发生变化func selector(_ selector: YDChannelSelector, didChangeDS newDataSource: [[SelectorItem]])/// 点击了关闭按钮func selector(_ selector: YDChannelSelector, dismiss newDataSource: [[SelectorItem]])/// 点击了某个频道func selector(_ selector: YDChannelSelector, didSelectChannel channelItem: SelectorItem)
}
复制代码

核心思路

如果你只是打算直接用的话那下面已经不用看了,因为以下是记录初版功能实现的核心思路以及难点介绍,如果感兴趣想自己扩展功能或者自定义的话可以看看。

写在前面: ios9以后苹果又添加了很多强大的api,所以本插件主要基于几个新api实现,整个逻辑还是很清晰明了。主要是很多细节比较恶心,后期调试了很久。

控件选择一眼就能看出 UICollectionView

private lazy var collectionView: UICollectionView = {let layout = UICollectionViewFlowLayout()layout.minimumLineSpacing = itemMarginlayout.minimumInteritemSpacing = itemMarginlayout.itemSize = CGSize(width: itemW, height: itemH)let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)cv.contentInset = UIEdgeInsets.init(top: 0, left: itemMargin, bottom: 0, right: itemMargin)cv.backgroundColor = UIColor.whitecv.showsVerticalScrollIndicator = falsecv.delegate = selfcv.dataSource = selfcv.register(YDChannelSelectorCell.self, forCellWithReuseIdentifier: YDChannelSelectorCellID)cv.register(YDChannelSelectorHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: YDChannelSelectorHeaderID)cv.addGestureRecognizer(longPressGes)return cv
}()
复制代码

最近删除 & 用户操作缓存

基于网易的逻辑,在操作时会出现一个新的section叫最近删除,dismiss时把最近删除的频道下移到我的栏目,思路就是在viewWillApperar时操纵数据源,添加最近删除section,在viewDidDisappear时整理用户操作,移除最近删除section,与此同时进行用户操作的缓存和读取,具体实现代码如下:

    public override func viewWillAppear(_ animated: Bool) {super.viewWillAppear(animated)// 根据需求处理数据源if isCacheLastest && UserDefaults.standard.value(forKey: operatedDS) != nil { // 需要缓存之前数据 且用户操作有存储// 缓存原始数据源if isCacheLastest { cacheDataSource(dataSource: dataSource!, isOrigin: true) }var bool = falselet newTitlesArrs = dataSource!.map { $0.map { $0.channelTitle! } }let orginTitlesArrs = UserDefaults.standard.value(forKey: originDS) as? [[String]]// 之前有存过原始数据源if orginTitlesArrs != nil { bool = newTitlesArrs == orginTitlesArrs! }if bool { // 和之前数据相等 -> 返回缓存数据源let cacheTitleArrs = UserDefaults.standard.value(forKey: operatedDS) as? [[String]]let flatArr = dataSource!.flatMap { $0 }var cachedDataSource = cacheTitleArrs!.map { $0.map { SelectorItem(channelTitle: $0, rawData: nil) }}for (i,items) in cachedDataSource.enumerated() {for (j,item) in items.enumerated() {for originItem in flatArr {if originItem.channelTitle == item.channelTitle {cachedDataSource[i][j] = originItem}}}}dataSource = cachedDataSource} else {  // 和之前数据不等 -> 返回新数据源(不处理)}}// 预处理数据源var dataSource_t = dataSourcedataSource_t?.insert(latelyDeleteChannels, at: 1)dataSource = dataSource_tcollectionView.reloadData()}public override func viewDidDisappear(_ animated: Bool) {super.viewDidDisappear(animated)// 移除界面后的一些操作dataSource![2] = dataSource![1] + dataSource![2]dataSource?.remove(at: 1)latelyDeleteChannels.removeAll()}
复制代码

用户操作相关

移动主要依赖9.0新增的InteractiveMovement系列接口,通过给collectionView添加长按手势并监听拖动的location实现item拖动效果:

@objc private func handleLongGesture(ges: UILongPressGestureRecognizer) {guard isEdit == true else { return }switch(ges.state) {case .began:guard let selectedIndexPath = collectionView.indexPathForItem(at: ges.location(in: collectionView)) else { break }collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)case .changed:collectionView.updateInteractiveMovementTargetPosition(ges.location(in: ges.view!))case .ended:collectionView.endInteractiveMovement()default:collectionView.cancelInteractiveMovement()}
}
复制代码

这里有个小坑就是cell自己的长按手势会和collectionView的长按手势冲突,需要在创建cell的时候做冲突解决:

public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {......// 手势冲突解决longPressGes.require(toFail: cell.longPressGes)......
}
复制代码

仔细观察发现网易的有个细节,就是点击item的时候要先闪烁一下在进入编辑状态,但是触碰事件会被collectionView拦截,所以要先自定义collectionView,重写func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?做下转换和提前处理:

fileprivate class HitTestView: UIView {open var collectionView: UICollectionView!/// 拦截系统触碰事件public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {if let indexPath = collectionView.indexPathForItem(at: convert(point, to: collectionView)) { // 在某个cell上let cell = collectionView.cellForItem(at: indexPath) as! YDChannelSelectorCellcell.touchAnimate()}return super.hitTest(point, with: event)}
}复制代码

在编辑模式频道不能拖到更多栏目里面,需要还原编辑动作,苹果提供了现成接口,我们只需要实现相应逻辑即可:

/// 这个方法里面控制需要移动和最后移动到的IndexPath(开始移动时)
/// - Returns: 当前期望移动到的位置
public func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {let item = dataSource![proposedIndexPath.section][proposedIndexPath.row]if proposedIndexPath.section > 0 || item.isFixation { // 不是我的栏目 或者是固定栏目return originalIndexPath} else {return proposedIndexPath}
}
复制代码

用户操作后的数据源处理

用户操作完后对数据源要操作方法是func handleDataSource(sourceIndexPath: IndexPath, destinationIndexPath: IndexPath), 调用时间有两个,一是拖动编辑后调用,二就是点击事件调用,为了数据源越界统一在此处理:

 private func handleDataSource(sourceIndexPath: IndexPath, destinationIndexPath: IndexPath) {let sourceStr = dataSource![sourceIndexPath.section][sourceIndexPath.row]if sourceIndexPath.section == 0 && destinationIndexPath.section == 1 { // 我的栏目 -> 最近删除latelyDeleteChannels.append(sourceStr)}if sourceIndexPath.section == 1 && destinationIndexPath.section == 0 && !latelyDeleteChannels.isEmpty { // 最近删除 -> 我的栏目latelyDeleteChannels.remove(at: sourceIndexPath.row)}dataSource![sourceIndexPath.section].remove(at: sourceIndexPath.row)dataSource![destinationIndexPath.section].insert(sourceStr, at: destinationIndexPath.row)// 通知代理delegate?.selector(self, didChangeDS: dataSource!)// 存储用户操作cacheDataSource(dataSource: dataSource!)
}
复制代码

以上就是项目核心思路和具体实现过程,欢迎使用,求Star~ 后续还会添加oc版本和外部的tab滑动条,敬请期待!你有好的建议或者问题也可随时pull request或者issue我。

转载于:https://juejin.im/post/5bfb91dee51d4548657d0265

高仿网易新闻频道选择器相关推荐

  1. Android高仿网易新闻客户端之动态添加标签

    承接上一篇文章:Android高仿网易新闻客户端之首页,今天来实现动态添加标签效果. 动态标签页是一个流式布局,实现了宽度自动换行高度自动分配的功能,代码如下: FlowLayout.java pac ...

  2. 【快速开发App实战】BUI高仿网易新闻App系列一、搭建App开发环境和工作空间

    一. 搭建App开发环境和工作空间 前言 我们的目标是要做一个真实的案例, 着重通过BUI框架及其相关工具的使用, 结合原生打包平台, 帮助大家理解一个App的开发过程. 以最新网易新闻的App为例, ...

  3. [分享]高仿网易新闻WebApp模板+Dcloud打包源码下载

    BUI-163网易新闻 大小: 6.27M 该App基于BUI Webapp框架+Dcloud构建. 仅供学习交流使用. 整个app开发过程记录在这里bui神速订阅号. 快速开发App系列篇 预览 交 ...

  4. 高仿网易新闻栏目动画效果

    tyktfj0910 的博客地址:http://blog.csdn.net/tyk0910 效果预览 今天准备用RecyclerView来实现网易新闻Tabs的动态效果.先看效果图: 点击下面的Rec ...

  5. Android高仿网易新闻客户端之首页

    关于实现网易新闻客户端的界面,以前写过很多博客,请参考: Android实现网易新闻客户端效果 Android实现网易新闻客户端侧滑菜单(一) Android实现网易新闻客户端侧滑菜单(二) 今天用V ...

  6. android 高仿网易新闻,Android高仿网易新闻客户端

    关于实现网易新闻客户端的界面,以前写过很多博客,请参考: 今天用ViewPager + FragmentAdapter + ViewPagerIndicator来实现. ViewPagerIndica ...

  7. Toolbar+DrawerLayout高仿网易新闻客户端

    首先看效果图,网易新闻客户端的特点是双向侧滑,并且左上角的图标会随着菜单的侧滑会有动画效果. 我们采用Toolbar和DrawerLayout实现双向侧滑以及actionbar 在菜单文件里先定义菜单 ...

  8. android高仿网易新闻上下拉刷新,仿网易新闻最新版的下拉刷新

    今天一天做了一个仿照网易的下拉刷新,先上效果图: 代码还有一点问题.我先说怎么用把: 1.   布局文件. xmlns:tools="http://schemas.android.com/t ...

  9. 仿网易新闻栏目管理(频道管理)功能

    仿网易新闻客户端栏目的拖拽,删除,添加效果.在此要感谢vipra,此效果是在这个项目的基础上修改的 先上效果图: 简单说一下实现原理: 首先看一下效果图,分为上下两个GridView,上边的为可以拖拽 ...

最新文章

  1. 敏捷开发实践—任务看板
  2. 经典C语言程序100例之十八
  3. 【Hadoop Summit Tokyo 2016】企业数据分类和治理
  4. 前端学习(554):node实现登录和注册第二部分代码
  5. Android 开源优秀 Library 推荐
  6. 为什么NX10帮助功能无法找到HTML,NX10.0 新功能介绍视频教程专辑
  7. getmany返回值 gjson_序列化多个模型并在一个JSON响应中发送所有Django Rest框架
  8. Python: SystemError: Unknown opcode
  9. 每当Xcode升级之后,都会导致原有的Xcode插件不能使用,解决办法
  10. 二维小游戏,飞机大战,图片素材
  11. 【信息学奥赛一本通】题解目录答案
  12. linux访问mdio接口函数,MII 接口解析(三)GPIO 模拟 MDIO 接口使用代码
  13. 单片机广告灯实验总结_单片机流水灯实验总结精选 .doc
  14. oracle pdb与cdb区别,CDB与PDB的系统关系
  15. 特征值和奇异值的关系
  16. 2021顺丰科技实习 面经
  17. IDEA+Java控制台实现宠物管理系统
  18. 连续复利怎么用计算机算,请问银行的连续复利计算公式
  19. 四、软件体系结构描述
  20. linux查看python解释器位置——及linux虚拟环境中的python解释器位置

热门文章

  1. 与 Josh Bloch 探讨 Java 未来
  2. 内存映射文件(File Mapping)API
  3. linux中使用net方式连接网络,如何使用 Netplan 从终端连接到无线网络?
  4. Vijos P1131 最小公倍数和最大公约数问题【暴力】
  5. TCP/IP详解学习笔记(13)-TCP坚持定时器,TCP保活定时器
  6. pat1085. Perfect Sequence (25)
  7. ubuntu查看端口占用
  8. Python 序列化 pickle/cPickle模块
  9. java线程系列---condition的讲解
  10. 聊天机器人中的深度学习技术(引言)