原文连接:模拟凤凰新闻 | 更复杂的标签动画 - 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 } } } }