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目錄中的代碼。