50天iOS挑战(Swift) - 第5天:模仿网易新闻顶端滑动分类列表
50天,每天一个Swift语言的iOS练手项目,覆盖iOS开发的主要知识。贵在坚持,重在思考
文章列表:http://blog.csdn.net/b735098742/article/category/6978601
Github项目:https://github.com/Minecodecraft/50DaysOfSwift
简介
本Demo模仿网易新闻的顶端分类列表,实现顶端ScrollView与CollectionView的交互,并在滑动或点击时完成字体变大变小等动态元素。 主要知识点: Collection View, UIScrollView,delegate
过程
1、 界面搭建
界面采用SB搭建,类似网易新闻即可。建立约束可能稍微复杂些。
2、 滚动条的实现
有以下两个操作可以改变滚动条与CollectionViewCell的index:
1. 点击ScrollView上的项目
2. 滚动CollectionView的Cell
前者可以直接在ScrollView中实现,而后者则需要考虑两个View之间的交互问题,我们先谈前者。
进度条为自定义的UIView,其上为列表对应的ScrollView以及Button。列表上的选项采用UILabel,选中时为18号红色字体,未选中时为14号黑色,对其点击的响应通过绑定手势监测器实现。
对列表项目点击的响应
由于我们将列表项对应的Label添加到了ScrollView中,我们可以直接通过其indexOfView方法获得对应的index
guard let view = gesture.view,
let index: Int = self.scrollView.subviews.index(of: view)
else { return }
而后直接放大所选项目和缩小原有项目即可,因为要提供给CollectionView,我们在此封装该方法。
/// 响应设置label比例的方法
///
/// - Parameters:
/// - scale: 变换的比例
/// - index: 当前页面索引
func setScale(withScale scale: CGFloat, forIndex index: Int) {
let label = self.scrollView.subviews[index] as! UILabel
label.textColor = UIColor.init(red: scale, green: 0, blue: 0, alpha: 1)
// 根据比例设置在14-18区间的字号
let fontSize = 14 + (18-14) * scale
label.transform = CGAffineTransform(scaleX: fontSize / 14, y: fontSize / 14)
//将label移动至中央
self.scrollToCenter(label)
}
同时,我们要在保持选中标签的居中,同样封装将Label移至中央的方法
/// 将label滑动至scrollView正中的方法
///
/// - Parameter view: 对应的label
func scrollToCenter(_ view: UIView) {
var offsetX = view.center.x - self.scrollView.frame.size.width * 0.5
// 如果标签旁边没有剩余标签,则不滚动
if offsetX < 0 {
offsetX = 0
}
self.scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
}
最后处理一下选中后Label样式变化的动画:我们在调用封装好的比例缩放方法时提交一个UIView动画即可
// 点击时,让点击的放大,其他缩小
UIView.animate(withDuration: 0.3) {
for i in 0..< self.scrollView.subviews.count {
let label = self.scrollView.subviews[i]
if label == gesture.view {
self.setScale(withScale: 1, forIndex: i)
} else {
self.setScale(withScale: 0, forIndex: i)
}
}
}
self.scrollToCenter(view)
这样我们就实现了滚动条以及点击滚动条的操作。关于选中后如何同步控制CollectionViewCell的问题,在后面讨论。
3、 CollectionViewCell的滚动
我们知道,当我们滚动Cell时,上面新闻分类的列表同样滚动,而与点击列表项目不同的是,滚动时列表项的Label样式是渐变的。逻辑不难理解,我们直接看代码:
// 当滑动的时候的响应
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// 计算整体的比例
let ratio: CGFloat = scrollView.contentOffset.x / scrollView.frame.size.width
// 计算出索引值
let index: Int = Int(ratio)
// 计算变化的比例
let scale: CGFloat = ratio - CGFloat(index)
// 修改channelView的大小
if index+1 < self.channels.count {
self.channelView.setScale(withScale: scale, forIndex: index+1)
self.channelView.setScale(withScale: 1 - scale, forIndex: index)
}
}
UICollectionView的代理方法实现不再赘述。
4、顶端滚动条点击时CollectionViewCell的同步响应
上面我们实现了分别创建滚动条和CollectionView,也实现了各自的逻辑。
由于CollectionView在Controller中,在滑动Cell时可以直接调用滚动条的方法控制滚动条选项的缩放等操作。而我们在点击滚动条选项时,如何让CollectionView也随之切换呢?
我们知道,如果在滚动条的类中保存一个指向控制器的强引用,则可以调用其方法来控制CollectionView,但是这样会造成强引用循环。若控制器弱引用View,View强引用控制器,则会在使用前已经释放View,且不遵守“对象不应该持有它的父对象”原则。这个问题可以通过代理来解决,即避免了强引用循环,也实现了代码的解耦。
我们对滚动条类定义如下协议:
protocol MCChannelViewDelegate {
func channelView(_ channelView: MCChannelView, forItemAt index: Int)
}
同时在类中声明代理:
var delegate: MCChannelViewDelegate?
而后在控制器中遵守协议并实现该代理方法即可:
/// 实现频道条操作时collectionView同步切换的方法
///
/// - Parameters:
/// - channelView: 操作对应的channelView
/// - index: 切换到的view的索引值
func channelView(_ channelView: MCChannelView, forItemAt index: Int) {
// 构造要滚动到的位置对应的indexPath
let indexPath = IndexPath(item: index, section: 0)
// 为了让点击的时候按钮能慢慢变大而不调用scrollViewDidScroll直接变大,要先不调用该方法
self.collectionView.delegate = nil
self.collectionView.scrollToItem(at: indexPath, at: .init(rawValue: 0), animated: false)
self.collectionView.delegate = self
}
至此,已经讲解了网易新闻顶端滑动分类列表的实现思路。具体细节请见项目Github的Day5目录中的代码。
|
请发表评论