50天iOS挑战(Swift) - 第5天:模仿网易新闻顶端滑动分类列表

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
GIF



过程

1、 界面搭建
界面采用SB搭建,类似网易新闻即可。建立约束可能稍微复杂些。
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目录中的代码。



项目源码地址:50DaysOfSwift,欢迎大家前来支持,随手丢一发Star

猜你喜欢

转载自blog.csdn.net/b735098742/article/details/78312351