原文連接:模擬鳳凰新聞 | 更復雜的標籤動畫 - Swift 實現多個 TableView 的側滑與切換
項目源碼:github 倉庫:模擬鳳凰新聞首頁ios
下午逛 SegmentFault 時看到有人問如何實現鳳凰新聞 app 首頁效果,正好這兩天在學習如何實現多個 TableView 的側滑與切換,索性本身嘗試一下。git
如圖:github
簡單列一下關鍵點:swift
跟隨滑動app
點擊事件ide
鳳凰新聞 app 裏面下劃線是在下面的 ScrollView 滾動動畫結束以後纔開始側滑的,因此須要監聽滾動是否結束。學習
我剛開始想用 scrollViewDidEndScrollingAnimation,結果並不行。這個方法具體使用場景我還沒搞清楚。測試
應該使用 scrollViewDidEndDecelerating,當 ScrollView 中止減速的時候即動畫結束的時候。字體
剛開始忘記了點擊事件,因此標籤用的 UILabel,其實能夠換成 UIButton。這樣就能省去尋找位置那一步。不過感受 UIButton 的樣式調整也很麻煩。幸運的是 UILabel 默認樣式(字體、字號)很是符合這個項目要求。動畫
不要忘記設置每一個 ScrollView 的 delegate;不要忘記在 delegate 方法中判斷當前是哪一個 ScrollView
// // ViewController.swift // FenghuangXinwen // // Created by Ant on 4/8/16. // Copyright © 2016 Ant. All rights reserved. // import UIKit class ViewController: UIViewController, UIScrollViewDelegate { @IBOutlet weak var tabScrollView: UIScrollView! @IBOutlet weak var contentScrollView: UIScrollView! let tabLine = UIView() //tab 標籤下劃線 let TAB_LINE_HEIGHT = CGFloat(2) //tab 標籤下劃線高度 let tabTitles = [ "頭條", "推薦", "娛樂", "財經", "自媒體", "鳳凰衛視", "科技", "良品", "美女", "軍事", "體育", "歷史", "汽車", "時尚", "房產", "FUN來了", "段子", "萌物", ] //tab 標籤標題 var tabLbls: [UILabel] = [] //tab 標籤對應的 UILabl //定義要用到的顏色及 RGB 值差,用於顏色變化 let TEXT_COLOR_NORMAL = UIColor(red: 115/255, green: 120/255, blue: 134/255, alpha: 1) let TEXT_COLOR_ACTIVE = UIColor(red: 245/255, green: 67/255, blue: 66/255, alpha: 1) let TEXT_COLOR_NORMAL_RED = CGFloat(115) let TEXT_COLOR_NORMAL_GREEN = CGFloat(120) let TEXT_COLOR_NORMAL_BLUE = CGFloat(134) let TEXT_COLOR_ACTIVE_RED = CGFloat(245) let TEXT_COLOR_ACTIVE_GREEN = CGFloat(67) let TEXT_COLOR_ACTIVE_BLUE = CGFloat(66) let TEXT_COLOR_RED_DIF = CGFloat(130) let TEXT_COLOR_GREEN_DIF = CGFloat(-53) let TEXT_COLOR_BLUE_DIF = CGFloat(-68) let TAB_LINE_COLOR = UIColor(red: 245/255, green: 67/255, blue: 66/255, alpha: 1) let MARGIN = CGFloat(20) //tab 標籤左右間距 var currentTabIndex = 0 //當前 tab 標籤 index var currentTabX = CGFloat(20) //當前 tab 標籤 x 座標,方便定位 override func viewDidLoad() { super.viewDidLoad() //設置 scrollView delegate tabScrollView.delegate = self contentScrollView.delegate = self //初始化視圖內容 initView() } func initView() { //定義一些常量方便使用 let TABSCROLLVIEW_HEIGHT = self.tabScrollView.frame.height //tabScrollview 高度 let LABEL_Y = TABSCROLLVIEW_HEIGHT / 2 - 5 // 每一個 tab 標籤的 y 座標 //生成 tab 標籤,添加到 tabScrollview 並設置大小位置 for var i = 0; i < self.tabTitles.count; i++ { let tabLbl = UILabel() tabLbl.text = tabTitles[i] tabLbl.textColor = self.TEXT_COLOR_NORMAL tabLbl.sizeToFit() tabLbls.append(tabLbl) self.tabScrollView.addSubview(tabLbl) if i > 0 { tabLbl.center = CGPointMake( self.MARGIN + self.tabLbls[i-1].center.x + self.tabLbls[i-1].frame.width / 2 + tabLbl.frame.width / 2 , LABEL_Y) } else { tabLbl.center = CGPointMake( self.MARGIN + tabLbl.frame.width / 2, LABEL_Y) } //順便生成並添加每一個 tab 頁面對應的 view。用於測試。 let tabContentView = UIView() self.contentScrollView.addSubview(tabContentView) tabContentView.backgroundColor = UIColor.whiteColor() tabContentView.frame = CGRectMake(self.view.frame.width * CGFloat(i), 0, self.view.frame.width, self.contentScrollView.frame.height) let labelInContent = UILabel() labelInContent.text = tabTitles[i] labelInContent.sizeToFit() tabContentView.addSubview(labelInContent) labelInContent.center = CGPointMake(tabContentView.frame.width / 2, tabContentView.frame.height / 2 - 100) } self.contentScrollView.contentSize = CGSizeMake(self.view.frame.width * CGFloat(self.tabLbls.count), self.contentScrollView.frame.height) //設置 contentScrollView 內容大小 //計算並設置 tabScrollView 內容大小 var TABVIEW_WIDTH = CGFloat(0) for tabLbl in self.tabLbls { TABVIEW_WIDTH += self.MARGIN + tabLbl.frame.width } TABVIEW_WIDTH += self.MARGIN self.tabScrollView.contentSize = CGSizeMake(TABVIEW_WIDTH, 40) //默認選中第一個標籤 self.tabLbls[0].textColor = self.TEXT_COLOR_ACTIVE self.currentTabIndex = 0 self.currentTabX = self.tabLbls[0].frame.origin.x //添加 tab 標籤下劃線 //設置位置有一個沒搞清楚的問題:不知爲什麼 y 座標設爲 TABSCROLLVIEW_HEIGHT - self.TAB_LINE_HEIGHT 時,下劃線看不見 self.tabScrollView.addSubview(self.tabLine) self.tabLine.backgroundColor = TAB_LINE_COLOR self.tabLine.frame = CGRectMake(MARGIN, TABSCROLLVIEW_HEIGHT - 5, self.tabLbls[0].frame.width, self.TAB_LINE_HEIGHT) } func scrollViewDidScroll(scrollView: UIScrollView) { //當 contentScrollView 滾動時 if scrollView == self.contentScrollView { let index = scrollView.contentOffset.x / self.view.frame.width //獲取當前頁面 index if floor(index) == index { self.currentTabIndex = Int(index) self.currentTabX = self.tabLbls[self.currentTabIndex].frame.origin.x } //阻止第一頁和最後一頁越界滾動 let MIN_X = CGFloat(0) let MAX_X = scrollView.contentSize.width - self.view.frame.width let CONTENT_OFFSET_X = scrollView.contentOffset.x if CONTENT_OFFSET_X < MIN_X { scrollView.contentOffset.x = MIN_X } else if CONTENT_OFFSET_X > MAX_X { scrollView.contentOffset.x = MAX_X } else { //當沒有越界時,執行『動畫』 //初始化一些要用到的值 let isLeft = index < CGFloat(self.currentTabIndex) let nextTabIndex = isLeft ? self.currentTabIndex - 1 : index == CGFloat(self.currentTabIndex) ? self.currentTabIndex : self.currentTabIndex + 1 //下一個標籤 index let currentTabWidth = self.tabLbls[self.currentTabIndex].frame.width //當前標籤寬度 let nextTabWidth = self.tabLbls[nextTabIndex].frame.width //下一個標籤寬度 let widthDif = nextTabWidth - currentTabWidth //兩個標籤寬度差 let distance = self.MARGIN + (isLeft ? self.tabLbls[nextTabIndex].frame.width : currentTabWidth) //下劃線須要滑動的距離 var offsetPercentage = index - CGFloat(self.currentTabIndex) //當前偏移百分比 //若是滑動超過一頁,將偏移百分比設置爲 ±1,避免多餘動畫 if offsetPercentage < -1 { offsetPercentage = -1 } if offsetPercentage > 1 { offsetPercentage = 1 } //改變標籤底部橫線位置和長度 self.tabLine.frame = CGRectMake(currentTabX + distance * offsetPercentage, self.tabLine.frame.origin.y, currentTabWidth + widthDif * abs(offsetPercentage), self.tabLine.frame.height) //改變顏色 self.tabLbls[nextTabIndex].textColor = UIColor(red: (TEXT_COLOR_NORMAL_RED + TEXT_COLOR_RED_DIF * abs(offsetPercentage)) / 255, green: (TEXT_COLOR_NORMAL_GREEN + TEXT_COLOR_GREEN_DIF * abs(offsetPercentage)) / 255, blue: (TEXT_COLOR_NORMAL_BLUE + TEXT_COLOR_BLUE_DIF * abs(offsetPercentage)) / 255, alpha: 1) self.tabLbls[self.currentTabIndex].textColor = UIColor(red: (TEXT_COLOR_ACTIVE_RED - TEXT_COLOR_RED_DIF * abs(offsetPercentage)) / 255, green: (TEXT_COLOR_ACTIVE_GREEN - TEXT_COLOR_GREEN_DIF * abs(offsetPercentage)) / 255, blue: (TEXT_COLOR_ACTIVE_BLUE - TEXT_COLOR_BLUE_DIF * abs(offsetPercentage)) / 255, alpha: 1) } } } func scrollViewDidEndDecelerating(scrollView: UIScrollView) { if scrollView == self.contentScrollView { let TWO_WORD_WIDTH = CGFloat(34) //兩個字標籤的寬度。這個間距實際上是根據本身需求隨便設置的。 //當標籤左邊被遮擋時,調整 tabScrollView x 軸偏移量 if self.tabLine.frame.origin.x < self.tabScrollView.contentOffset.x { UIView.animateWithDuration(0.4, delay: 0, options: [.CurveEaseInOut], animations: { () -> Void in self.tabScrollView.contentOffset.x = self.tabLine.frame.origin.x - self.MARGIN }, completion: nil) } //當下劃線 x 座標在 tabScrollView 中部以後時,調整 tabScrollView x 軸偏移量 if self.tabLine.frame.origin.x > self.tabScrollView.frame.width / 2 && self.currentTabIndex + 1 < self.tabLbls.count - 3 { UIView.animateWithDuration(0.4, delay: 0, options: [.CurveEaseInOut], animations: { () -> Void in self.tabScrollView.contentOffset.x = self.tabLine.frame.origin.x - self.MARGIN - TWO_WORD_WIDTH }, completion: nil) } //當標籤右邊被遮擋時,調整 tabScrollView x 軸偏移量 if self.tabLine.frame.origin.x + self.tabLine.frame.width + self.MARGIN > self.tabScrollView.contentOffset.x + self.tabScrollView.frame.width{ UIView.animateWithDuration(0.4, delay: 0, options: [.CurveEaseInOut], animations: { () -> Void in self.tabScrollView.contentOffset.x += (self.tabLine.frame.origin.x + self.tabLine.frame.width + self.MARGIN) - (self.tabScrollView.contentOffset.x + self.tabScrollView.frame.width) + self.MARGIN }, completion: nil) } } } @IBAction func tabScrollViewTapped(sender: UITapGestureRecognizer) { let location = sender.locationInView(self.tabScrollView) //獲取當前點擊事件在 tabScrollView 裏的座標 //循環找到點擊的是哪個標籤,找到時執行方法 for var i = 0; i < self.tabLbls.count; i++ { if CGRectContainsPoint(self.tabLbls[i].frame, location) { self.tabLbls[self.currentTabIndex].textColor = TEXT_COLOR_NORMAL self.tabLbls[i].textColor = TEXT_COLOR_ACTIVE self.currentTabIndex = i self.currentTabX = self.tabLbls[self.currentTabIndex].frame.origin.x self.contentScrollView.contentOffset.x = self.view.frame.width * CGFloat(i) break } } } }