布局首页分析实现
完成最终效果
- 这个效果还是挺常见的哈

- 效果分析:
- 顶部是一个工具类,并且为了该工具类可以复用。(其他界面也用到)最好是单独封装起来

- 中间内容可以左右滚动,并且有分页效果。可以通过封装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) }}