布局首页分析实现
完成最终效果
- 这个效果还是挺常见的哈
![](http://bbs.520it.com/editormd/uploads/201609/20160914084809_285271.gif)
- 效果分析:
- 顶部是一个工具类,并且为了该工具类可以复用。(其他界面也用到)最好是单独封装起来
![](http://bbs.520it.com/editormd/uploads/201609/20160914084833_470852.png)
- 中间内容可以左右滚动,并且有分页效果。可以通过封装UIView,并且里面添加UICollectionView方式。
![](http://bbs.520it.com/editormd/uploads/201609/20160914084842_917858.png)
封装顶部的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)
}
}