佈局首頁分析實現
完成最終效果
- 這個效果還是挺常見的哈
- 效果分析:
- 頂部是一個工具類,並且爲了該工具類可以複用。(其他界面也用到)最好是單獨封裝起來
- 中間內容可以左右滾動,並且有分頁效果。可以通過封裝UIView,並且裏面添加UICollectionView方式。
封裝頂部的PageTitleView
封裝構造函數
- 封裝構造函數,讓別人在創建對象時,就傳入其實需要顯示的內容
- frame:創建對象時確定了frame就可以直接設置子控件的位置和尺寸
- isScrollEnable:是否可以滾動。某些地方該控件是可以滾動的。
- titles:顯示的所有標題
// MARK:- 構造函數
init(frame: CGRect, isScrollEnable : Bool, titles : [String]) {
self.isScrollEnable = isScrollEnable
self.titles = titles
super.init(frame: frame)
}
設置UI界面
- 設置UI界面
- 添加UIScrollView,如果標題過多,則可以滾動
- 初始化所有的Label,用於顯示標題。並且給label添加監聽手勢
- 添加頂部線和滑塊的View
private lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView(frame: self.bounds)
scrollView.showsHorizontalScrollIndicator = false
scrollView.scrollsToTop = false
scrollView.bounces = false
return scrollView
}()
private lazy var scrollLine : UIView = {
let scrollLine = UIView()
scrollLine.backgroundColor = kSelectTitleColor
return scrollLine
}()
private func setupUI() {
// 1.添加scrollView
addSubview(scrollView)
// 2.初始化labels
setupTitleLabels()
// 3.添加定義的線段和滑動的滑塊
setupBottomlineAndScrollline()
}
private func setupTitleLabels() {
let titleY : CGFloat = 0
let titleH : CGFloat = bounds.height - kScrollLineH
let count = titles.count
for (index, title) in titles.enumerate() {
// 1.創建Label
let label = UILabel()
// 2.設置Label的屬性
label.text = title
label.tag = index
label.textAlignment = .Center
label.textColor = kNormalTitleColor
label.font = UIFont.systemFontOfSize(16.0)
titleLabels.append(label)
// 3.設置label的frame
var titleW : CGFloat = 0
var titleX : CGFloat = 0
if !isScrollEnable {
titleW = bounds.width / CGFloat(count)
titleX = CGFloat(index) * titleW
} else {
let size = (title as NSString).boundingRectWithSize(CGSizeMake(CGFloat(MAXFLOAT), 0), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName : label.font], context: nil)
titleW = size.width
if index != 0 {
titleX = CGRectGetMaxX(titleLabels[index - 1].frame) + kTitleMargin
}
}
label.frame = CGRect(x: titleX, y: titleY, width: titleW, height: titleH)
// 4.將Label添加到父控件中
scrollView.addSubview(label)
// 5.監聽label的點擊
label.userInteractionEnabled = true
let tapGes = UITapGestureRecognizer(target: self, action: #selector(self.titleLabelClick(_:)))
label.addGestureRecognizer(tapGes)
}
}
private func setupBottomlineAndScrollline() {
// 1.添加bottomline
let bottomline = UIView()
bottomline.frame = CGRect(x: 0, y: bounds.height - 0.5, width: bounds.width, height: 0.5)
bottomline.backgroundColor = UIColor.lightGrayColor()
addSubview(bottomline)
// 2.設置滑塊的view
addSubview(scrollLine)
guard let firstLabel = titleLabels.first else { return }
let lineX = firstLabel.frame.origin.x
let lineY = bounds.height - kScrollLineH
let lineW = firstLabel.frame.width
let lineH = kScrollLineH
scrollLine.frame = CGRect(x: lineX, y: lineY, width: lineW, height: lineH)
firstLabel.textColor = kSelectTitleColor
}
封裝頂部的PageCotentView
封裝構造函數
- 封裝構造函數,讓別人在創建對象時,就傳入其實需要顯示的內容
- 所有用於顯示在UICollectionView的Cell的所有控制器
- 控制器的父控制器
// MARK:- 構造函數
init(frame: CGRect, childVcs : [UIViewController], parentViewController : UIViewController) {
self.childVcs = childVcs
self.parentViewController = parentViewController
super.init(frame: frame)
}
設置UI界面內容
- 設置UI界面
- 將所有的子控制器添加到父控制器中
- 添加UICollectionView,用於展示內容
// MARK:- 懶加載屬性
private lazy var collectionView : UICollectionView = {
// 1.創建佈局
let layout = UICollectionViewFlowLayout()
layout.itemSize = self.bounds.size
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .Horizontal
// 2.創建collectionView
let collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
collectionView.showsHorizontalScrollIndicator = false
collectionView.pagingEnabled = true
collectionView.bounces = false
collectionView.scrollsToTop = false
collectionView.dataSource = self
collectionView.delegate = self
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: kContentCellID)
return collectionView
}()
private func setupUI() {
// 1.添加所有的控制器
for childVc in childVcs {
parentViewController?.addChildViewController(childVc)
}
// 2.添加collectionView
addSubview(collectionView)
}
實現UICollectionView的數據源方法
- 在返回Cell的方法中,先將cell的contentView中的子控件都移除,防止循環引用造成問題
- 取出indexPath.item對應的控制器,將控制器的View添加到Cell的contentView中
// MARK:- 遵守UICollectionView的數據源
extension PageContentView : UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return childVcs.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(kContentCellID, forIndexPath: indexPath)
// 移除之前的
for subview in cell.contentView.subviews {
subview.removeFromSuperview()
}
// 取出控制器
let childVc = childVcs[indexPath.item]
childVc.view.frame = cell.contentView.bounds
cell.contentView.addSubview(childVc.view)
return cell
}
}
PageTitleView點擊改變PageContentView
- 通過代理將PageTitleView的事件傳遞出去
/// 定義協議
protocol PageTitleViewDelegate : class {
func pageTitleView(pageTitleView : PageTitleView, didSelectedIndex index : Int)
}
@objc private func titleLabelClick(tapGes : UITapGestureRecognizer) {
// 1.獲取點擊的下標誌
guard let view = tapGes.view else { return }
let index = view.tag
// 2.滾到正確的位置
scrollToIndex(index)
// 3.通知代理
delegate?.pageTitleView(self, didSelectedIndex: index)
}
// 內容滾動
private func scrollToIndex(index : Int) {
// 1.獲取最新的label和之前的label
let newLabel = titleLabels[index]
let oldLabel = titleLabels[currentIndex]
// 2.設置label的顏色
newLabel.textColor = kSelectTitleColor
oldLabel.textColor = kNormalTitleColor
// 3.scrollLine滾到正確的位置
let scrollLineEndX = scrollLine.frame.width * CGFloat(index)
UIView.animateWithDuration(0.15) {
self.scrollLine.frame.origin.x = scrollLineEndX
}
// 4.記錄index
currentIndex = index
}
- 在PageContentView中設置當前應該滾動的位置
// MARK:- 對外暴露方法
extension PageContentView {
func scrollToIndex(index : Int) {
let offset = CGPoint(x: CGFloat(index) * collectionView.bounds.width, y: 0)
collectionView.setContentOffset(offset, animated: false)
}
}
PageContentView滾動調整PageTitleView
- 首先在scrollViewDidScroll的代理方法中就可以監聽滾動
- 那麼滾動時,我們有哪些內容是需要傳遞出去呢?
- 1> 原來位置的Title顏色會逐漸變暗
- 2> 目標位置的Title顏色會逐漸變亮
- 3> 變化程度是和滾動的多少相關
- 由此得出結論:
- 我們一共需要獲取三個值,並且將這三個值傳遞出去
- 1> 起始位置下標值
- 2> 目標位置下標值
- 3> 當前滾動的進度
- 圖例分析(缺)
- 左右滑動時,下標值、進度是不同的
- 需要經過判斷來獲取,並且下標值需要防止越界問題
- 注意:當左滑結束時,此時再+1會出錯。因此必須加上targetIndex = sourceIndex,且進度爲1
- 實現代碼
extension PageContentView : UICollectionViewDelegate {
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
startOffsetX = scrollView.contentOffset.x
}
func scrollViewDidScroll(scrollView: UIScrollView) {
// 1.定義要獲取的內容
var sourceIndex = 0
var targetIndex = 0
var progress : CGFloat = 0
// 2.獲取進度
let offsetX = scrollView.contentOffset.x
let ratio = offsetX / scrollView.bounds.width
progress = ratio - floor(ratio)
// 3.判斷滑動的方向
if offsetX > startOffsetX { // 向左滑動
sourceIndex = Int(offsetX / scrollView.bounds.width)
targetIndex = sourceIndex + 1
if targetIndex >= childVcs.count {
targetIndex = childVcs.count - 1
}
if offsetX - startOffsetX == scrollView.bounds.width {
progress = 1.0
targetIndex = sourceIndex
}
} else { // 向右滑動
targetIndex = Int(offsetX / scrollView.bounds.width)
sourceIndex = targetIndex + 1
if sourceIndex >= childVcs.count {
sourceIndex = childVcs.count - 1
}
progress = 1 - progress
}
// 4.通知代理
delegate?.pageContentView(self, sourceIndex: sourceIndex, targetIndex: targetIndex, progress: progress)
}
}
- 根據滾動傳入的值,調整PageTitleView
- 兩種顏色必須使用RGB值設置(方便通過RGB實現漸變效果)
private let kNormalRGB : (CGFloat, CGFloat, CGFloat) = (85, 85, 85)
private let kSelectRGB : (CGFloat, CGFloat, CGFloat) = (255, 128, 0)
private let kDeltaRGB = (kSelectRGB.0 - kNormalRGB.0, kSelectRGB.1 - kNormalRGB.1, kSelectRGB.2 - kNormalRGB.2)
private let kNormalTitleColor = UIColor(red: 85/255.0, green: 85/255.0, blue: 85/255.0, alpha: 1.0)
private let kSelectTitleColor = UIColor(red: 255.0/255.0, green: 128/255.0, blue: 0/255.0, alpha: 1.0)
// MARK:- 對外暴露方法
extension PageTitleView {
func setCurrentTitle(sourceIndex : Int, targetIndex : Int, progress : CGFloat) {
// 1.取出兩個Label
let sourceLabel = titleLabels[sourceIndex]
let targetLabel = titleLabels[targetIndex]
// 2.移動scrollLine
let moveMargin = targetLabel.frame.origin.x - sourceLabel.frame.origin.x
scrollLine.frame.origin.x = sourceLabel.frame.origin.x + moveMargin * progress
// 3.顏色漸變
sourceLabel.textColor = UIColor(red: (kSelectRGB.0 - kDeltaRGB.0 * progress) / 255.0, green: (kSelectRGB.1 - kDeltaRGB.1 * progress) / 255.0, blue: (kSelectRGB.2 - kDeltaRGB.2 * progress) / 255.0, alpha: 1.0)
targetLabel.textColor = UIColor(red: (kNormalRGB.0 + kDeltaRGB.0 * progress)/255.0, green: (kNormalRGB.1 + kDeltaRGB.1 * progress)/255.0, blue: (kNormalRGB.2 + kDeltaRGB.2 * progress)/255.0, alpha: 1.0)
}
}