天天看点

50天iOS挑战(Swift) - 第5天:模仿网易新闻顶端滑动分类列表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

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

过程

1、 界面搭建

界面采用SB搭建,类似网易新闻即可。建立约束可能稍微复杂些。

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

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