高仿网易新闻频道选择器
前言
前段时间公司做一个新闻类的项目,需要支持频道编辑,缓存等功能,界面效果逻辑就按照最新版的网易新闻来,网上没找到类似的轮子,二话不说直接开撸,为了做到和网易效果一模一样还是遇到不少坑和细节,这在此分享出来,自己做个记录,大家觉得有用的话也可以参考。支持手动集成或者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
高仿网易新闻频道选择器相关推荐
- Android高仿网易新闻客户端之动态添加标签
承接上一篇文章:Android高仿网易新闻客户端之首页,今天来实现动态添加标签效果. 动态标签页是一个流式布局,实现了宽度自动换行高度自动分配的功能,代码如下: FlowLayout.java pac ...
- 【快速开发App实战】BUI高仿网易新闻App系列一、搭建App开发环境和工作空间
一. 搭建App开发环境和工作空间 前言 我们的目标是要做一个真实的案例, 着重通过BUI框架及其相关工具的使用, 结合原生打包平台, 帮助大家理解一个App的开发过程. 以最新网易新闻的App为例, ...
- [分享]高仿网易新闻WebApp模板+Dcloud打包源码下载
BUI-163网易新闻 大小: 6.27M 该App基于BUI Webapp框架+Dcloud构建. 仅供学习交流使用. 整个app开发过程记录在这里bui神速订阅号. 快速开发App系列篇 预览 交 ...
- 高仿网易新闻栏目动画效果
tyktfj0910 的博客地址:http://blog.csdn.net/tyk0910 效果预览 今天准备用RecyclerView来实现网易新闻Tabs的动态效果.先看效果图: 点击下面的Rec ...
- Android高仿网易新闻客户端之首页
关于实现网易新闻客户端的界面,以前写过很多博客,请参考: Android实现网易新闻客户端效果 Android实现网易新闻客户端侧滑菜单(一) Android实现网易新闻客户端侧滑菜单(二) 今天用V ...
- android 高仿网易新闻,Android高仿网易新闻客户端
关于实现网易新闻客户端的界面,以前写过很多博客,请参考: 今天用ViewPager + FragmentAdapter + ViewPagerIndicator来实现. ViewPagerIndica ...
- Toolbar+DrawerLayout高仿网易新闻客户端
首先看效果图,网易新闻客户端的特点是双向侧滑,并且左上角的图标会随着菜单的侧滑会有动画效果. 我们采用Toolbar和DrawerLayout实现双向侧滑以及actionbar 在菜单文件里先定义菜单 ...
- android高仿网易新闻上下拉刷新,仿网易新闻最新版的下拉刷新
今天一天做了一个仿照网易的下拉刷新,先上效果图: 代码还有一点问题.我先说怎么用把: 1. 布局文件. xmlns:tools="http://schemas.android.com/t ...
- 仿网易新闻栏目管理(频道管理)功能
仿网易新闻客户端栏目的拖拽,删除,添加效果.在此要感谢vipra,此效果是在这个项目的基础上修改的 先上效果图: 简单说一下实现原理: 首先看一下效果图,分为上下两个GridView,上边的为可以拖拽 ...
最新文章
- 敏捷开发实践—任务看板
- 经典C语言程序100例之十八
- 【Hadoop Summit Tokyo 2016】企业数据分类和治理
- 前端学习(554):node实现登录和注册第二部分代码
- Android 开源优秀 Library 推荐
- 为什么NX10帮助功能无法找到HTML,NX10.0 新功能介绍视频教程专辑
- getmany返回值 gjson_序列化多个模型并在一个JSON响应中发送所有Django Rest框架
- Python: SystemError: Unknown opcode
- 每当Xcode升级之后,都会导致原有的Xcode插件不能使用,解决办法
- 二维小游戏,飞机大战,图片素材
- 【信息学奥赛一本通】题解目录答案
- linux访问mdio接口函数,MII 接口解析(三)GPIO 模拟 MDIO 接口使用代码
- 单片机广告灯实验总结_单片机流水灯实验总结精选 .doc
- oracle pdb与cdb区别,CDB与PDB的系统关系
- 特征值和奇异值的关系
- 2021顺丰科技实习 面经
- IDEA+Java控制台实现宠物管理系统
- 连续复利怎么用计算机算,请问银行的连续复利计算公式
- 四、软件体系结构描述
- linux查看python解释器位置——及linux虚拟环境中的python解释器位置
热门文章
- 与 Josh Bloch 探讨 Java 未来
- 内存映射文件(File Mapping)API
- linux中使用net方式连接网络,如何使用 Netplan 从终端连接到无线网络?
- Vijos P1131 最小公倍数和最大公约数问题【暴力】
- TCP/IP详解学习笔记(13)-TCP坚持定时器,TCP保活定时器
- pat1085. Perfect Sequence (25)
- ubuntu查看端口占用
- Python 序列化 pickle/cPickle模块
- java线程系列---condition的讲解
- 聊天机器人中的深度学习技术(引言)