//
// TGPageView.swift
// TGPageView
//
// Created by targetcloud on 2017/3/22.
// Copyright © 2017年 targetcloud. All rights reserved.
//
import UIKit
class TGPageView: UIView {
fileprivate var titles : [String]
fileprivate var childVCs : [UIViewController]
fileprivate var parentVC : UIViewController
fileprivate var titleStyle : TGPageStyle
init(frame: CGRect,titles : [String],titleStyle : TGPageStyle,childVCs : [UIViewController],parentVC : UIViewController) {
self.titles = titles
self.childVCs = childVCs
self.parentVC = parentVC
self.titleStyle = titleStyle
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension TGPageView{
fileprivate func setupUI(){
let titleViewFrame = CGRect(x: 0, y: 0, width: bounds.width, height: titleStyle.titleViewHeight)
let titleView = TGTitleView(frame:titleViewFrame,titles:titles,style:titleStyle)
titleView.backgroundColor = UIColor.orange
addSubview(titleView)
let contentFrame = CGRect(x: 0, y: titleViewFrame.maxY, width: bounds.width, height: bounds.height - titleStyle.titleViewHeight)
let contentView = TGContentView(frame: contentFrame, childVCs: childVCs, parentVC: parentVC)
contentView.backgroundColor = .red
addSubview(contentView)
//titleView与contentView进行协作
//MARK:- 代理 1
titleView.delegate = contentView
//MARK:- 代理的使用 1
contentView.delegate = titleView
}
}
//
// TGTitleView.swift
// TGPageView
//
// Created by targetcloud on 2017/3/22.
// Copyright © 2017年 targetcloud. All rights reserved.
//
import UIKit
//MARK:delegate 1
protocol TGTitleViewDelegate:class {//1 NSObjectProtocol 2 class 只能被类遵守
func titleView(_ titleView :TGTitleView,targetIndex:Int)
}
class TGTitleView: UIView {
//MARK:delegate 2
weak var delegate : TGTitleViewDelegate?
fileprivate var titles : [String]
fileprivate var titleStyle : TGPageStyle
fileprivate lazy var scrollView : UIScrollView = {
let sv = UIScrollView(frame:self.bounds)
sv.showsHorizontalScrollIndicator = false
sv.scrollsToTop = false//点击状态栏不要回到顶部
return sv
}()
fileprivate var currentIndex = 0
fileprivate lazy var titleLabels : [UILabel] = [UILabel]()
fileprivate lazy var normalRGB :(CGFloat,CGFloat,CGFloat) = self.titleStyle.normalColor.getRGB()
fileprivate lazy var selectRGB :(CGFloat,CGFloat,CGFloat) = self.titleStyle.selectColor.getRGB()
fileprivate lazy var deltaRGB :(CGFloat,CGFloat,CGFloat) = {
let deltaR = self.selectRGB.0 - self.normalRGB.0
let deltaG = self.selectRGB.1 - self.normalRGB.1
let deltaB = self.selectRGB.2 - self.normalRGB.2
return (deltaR,deltaG,deltaB)
}()
fileprivate lazy var bottomLine : UIView = {
let bottomLine = UIView()
bottomLine.backgroundColor = self.titleStyle.bottomLineColor
return bottomLine
}()
fileprivate lazy var coverView : UIView = {
let coverV = UIView()
coverV.backgroundColor = self.titleStyle.coverBgColor
coverV.alpha = self.titleStyle.coverAlpha
return coverV
}()
init(frame: CGRect,titles : [String],style : TGPageStyle) {
self.titles = titles
self.titleStyle = style
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension TGTitleView {
fileprivate func setupUI(){
addSubview(scrollView)
setupLabels()
setupBottomLine()
setupCoverView()
}
private func setupCoverView(){
if titleStyle.isShowCoverView{
scrollView.insertSubview(coverView, at: 0)//scrollView.addSubview(coverView)
guard let firstLabel = titleLabels.first else {
return
}
//MARK:- 可滚动不可滚动 有下划线没有下划线 共4种组合情况的处理 1
var coverX : CGFloat = titleStyle.isShowBottomLine ? bottomLine.frame.origin.x : firstLabel.frame.origin.x
let coverY : CGFloat = (firstLabel.frame.height - titleStyle.coverHeight) * 0.5//let coverY : CGFloat = (titleStyle.titleViewHeight - titleStyle.coverHeight) * 0.5
var coverW : CGFloat = titleStyle.isShowBottomLine ? bottomLine.frame.width : firstLabel.frame.width
let coverH : CGFloat = titleStyle.coverHeight
//没有滚动条且可滚动
if !titleStyle.isShowBottomLine && titleStyle.isScrollEnable{
coverX -= titleStyle.coverMargin
coverW += titleStyle.coverMargin * 2
}
//没有滚动条且不可滚动
if !titleStyle.isShowBottomLine && !titleStyle.isScrollEnable{//等分
coverX += titleStyle.coverMargin
coverW -= titleStyle.coverMargin * 2
}
coverView.frame = CGRect(x: coverX, y: coverY, width: coverW, height: coverH)
coverView.layer.cornerRadius = titleStyle.coverRadius
coverView.layer.masksToBounds = true
}
}
private func setupBottomLine(){
if titleStyle.isShowBottomLine {
scrollView.addSubview(bottomLine)
bottomLine.frame = titleLabels.first!.frame
//MARK:- 等分情况下的滚动条 1
if !titleStyle.isScrollEnable{
bottomLine.frame.origin.x += titleStyle.bottomLineExtendWidth
bottomLine.frame.size.width -= 2 * titleStyle.bottomLineExtendWidth
}else{
bottomLine.frame.origin.x -= titleStyle.bottomLineExtendWidth
bottomLine.frame.size.width += 2 * titleStyle.bottomLineExtendWidth
}
bottomLine.frame.size.height = titleStyle.bottomLineHeight
bottomLine.frame.origin.y = titleStyle.titleViewHeight - titleStyle.bottomLineHeight - titleStyle.bottomLineMargin
}
}
private func setupLabels(){
for (i,title) in titles.enumerated(){
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.tag = i
titleLabel.textAlignment = .center
titleLabel.textColor = (i==0 ? titleStyle.selectColor : titleStyle.normalColor)
titleLabel.font = titleStyle.titleFont
scrollView.addSubview(titleLabel)
let tapGes = UITapGestureRecognizer(target: self, action: #selector(titleLabelClick))
titleLabel.addGestureRecognizer(tapGes)
titleLabel.isUserInteractionEnabled = true
titleLabels.append(titleLabel)
}
var labelW : CGFloat = bounds.width / CGFloat(titleLabels.count)
let labelH : CGFloat = titleStyle.titleViewHeight
var labelX : CGFloat = 0
let labelY : CGFloat = 0
for (i,titleLabel) in titleLabels.enumerated(){
if titleStyle.isScrollEnable{
//可以滚动
let size = CGSize(width: CGFloat(MAXFLOAT), height: 0)
let attributes = [NSFontAttributeName : titleStyle.titleFont]
labelW = (titleLabel.text! as NSString).boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil).width
labelX = ( i==0 ? titleStyle.titleMargin * 0.5 : (titleLabels[i-1].frame.maxX + titleStyle.titleMargin ))
}else{
//不可以滚动 平分
labelX = labelW * CGFloat(i)
}
titleLabel.frame = CGRect(x: labelX, y: labelY, width: labelW, height: labelH)
}
if titleStyle.isScrollEnable{
scrollView.contentSize = CGSize(width: (titleLabels.last?.frame.maxX)! + titleStyle.titleMargin * 0.5, height: 0)
}
if titleStyle.isNeedTitleScale{
titleLabels.first?.transform = CGAffineTransform(scaleX: titleStyle.scaleRange, y: titleStyle.scaleRange)
}
}
}
extension TGTitleView{
@objc fileprivate func titleLabelClick(_ tapGes:UITapGestureRecognizer){
guard let targetLabel = tapGes.view as? UILabel else{
return
}
guard targetLabel.tag != currentIndex else {
return
}
let sourceLabel = titleLabels[currentIndex]
sourceLabel.textColor = titleStyle.normalColor
targetLabel.textColor = titleStyle.selectColor
currentIndex = targetLabel.tag
//调整中间位置offset
adjustLabelPos()
//MARK:delegate 3
delegate?.titleView(self, targetIndex: currentIndex)
if titleStyle.isNeedTitleScale{
UIView.animate(withDuration: 0.5, animations: {
sourceLabel.transform = CGAffineTransform.identity
targetLabel.transform = CGAffineTransform(scaleX: self.titleStyle.scaleRange, y: self.titleStyle.scaleRange)
})
}
if titleStyle.isShowBottomLine {
//x & width bounds是等于frame与Transform比例计算得来的,bounds不等于frame的宽高
UIView.animate(withDuration: 0.5, animations: {
//MARK:- 等分情况下的滚动条 2
if !self.titleStyle.isScrollEnable{
self.bottomLine.frame.origin.x = targetLabel.frame.origin.x + self.titleStyle.bottomLineExtendWidth
self.bottomLine.frame.size.width = targetLabel.frame.width - 2 * self.titleStyle.bottomLineExtendWidth
}else{
self.bottomLine.frame.origin.x = targetLabel.frame.origin.x - self.titleStyle.bottomLineExtendWidth
self.bottomLine.frame.size.width = targetLabel.frame.width + 2 * self.titleStyle.bottomLineExtendWidth
}
})
}
//MARK:- 可滚动不可滚动 有下划线没有下划线 共4种组合情况的处理 2
if titleStyle.isShowCoverView{
UIView.animate(withDuration: 0.5, animations: {
self.coverView.frame.origin.x = self.titleStyle.isShowBottomLine ? self.bottomLine.frame.origin.x : targetLabel.frame.origin.x
self.coverView.frame.size.width = self.titleStyle.isShowBottomLine ? self.bottomLine.frame.width : targetLabel.frame.width
//没有滚动条且可滚动
if !self.titleStyle.isShowBottomLine && self.titleStyle.isScrollEnable{
self.coverView.frame.origin.x -= self.titleStyle.coverMargin
self.coverView.frame.size.width += self.titleStyle.coverMargin * 2
}
//没有滚动条且不可滚动
if !self.titleStyle.isShowBottomLine && !self.titleStyle.isScrollEnable{//等分
self.coverView.frame.origin.x += self.titleStyle.coverMargin
self.coverView.frame.size.width -= self.titleStyle.coverMargin * 2
}
})
}
}
fileprivate func adjustLabelPos(){
guard titleStyle.isScrollEnable else {
return
}
let targetLabel = titleLabels[currentIndex]
var offset = targetLabel.center.x - scrollView.frame.width * 0.5
// print(offset)
if offset < 0{
offset = 0
}
let maxOffset = scrollView.contentSize.width - scrollView.bounds.width
if offset > maxOffset{
offset = maxOffset
}
scrollView.setContentOffset(CGPoint(x:offset,y:0), animated: true)
}
}
//MARK:- 代理的使用 2
extension TGTitleView:TGContentViewDelegate{
func contentView(_ contentView: TGContentView, didEndScroll inIndex: Int) {
currentIndex = inIndex
//调整中间位置offset
adjustLabelPos()
}
func contentView(_ contentView: TGContentView, sourceIndex: Int, targetIndex: Int, progress: CGFloat) {
let sourceLabel = titleLabels[sourceIndex]
let targetLabel = titleLabels[targetIndex]
sourceLabel.textColor = UIColor(r: selectRGB.0 - deltaRGB.0 * progress, g: selectRGB.1 - deltaRGB.1 * progress, b: selectRGB.2 - deltaRGB.2 * progress)
targetLabel.textColor = UIColor(r: normalRGB.0 + deltaRGB.0 * progress, g: normalRGB.1 + deltaRGB.1 * progress, b: normalRGB.2 + deltaRGB.2 * progress)
if titleStyle.isNeedTitleScale{
let deltaScale = titleStyle.scaleRange - 1.0
sourceLabel.transform = CGAffineTransform(scaleX: titleStyle.scaleRange - deltaScale * progress, y: titleStyle.scaleRange - deltaScale * progress)
targetLabel.transform = CGAffineTransform(scaleX: 1 + deltaScale * progress, y: 1 + deltaScale * progress)
}
let deltaWidth = targetLabel.frame.width - sourceLabel.frame.width
let deltaX = targetLabel.frame.origin.x - sourceLabel.frame.origin.x
//x & width
if titleStyle.isShowBottomLine{
//MARK:- 等分情况下的滚动条 3
if !titleStyle.isScrollEnable{
bottomLine.frame.origin.x = sourceLabel.frame.origin.x + deltaX * progress + titleStyle.bottomLineExtendWidth
bottomLine.frame.size.width = sourceLabel.frame.width + deltaWidth * progress - 2 * titleStyle.bottomLineExtendWidth
}else{
bottomLine.frame.origin.x = sourceLabel.frame.origin.x + deltaX * progress - titleStyle.bottomLineExtendWidth
bottomLine.frame.size.width = sourceLabel.frame.width + deltaWidth * progress + 2 * titleStyle.bottomLineExtendWidth
}
}
//MARK:- 可滚动不可滚动 有下划线没有下划线 共4种组合情况的处理 3
if titleStyle.isShowCoverView{
coverView.frame.origin.x = titleStyle.isShowBottomLine ? bottomLine.frame.origin.x : (sourceLabel.frame.origin.x + deltaX * progress)
coverView.frame.size.width = titleStyle.isShowBottomLine ? bottomLine.frame.width : (sourceLabel.frame.width + deltaWidth * progress)
//没有滚动条且可滚动
if !titleStyle.isShowBottomLine && titleStyle.isScrollEnable{
coverView.frame.origin.x -= titleStyle.coverMargin
coverView.frame.size.width += titleStyle.coverMargin * 2
}
//没有滚动条且不可滚动
if !titleStyle.isShowBottomLine && !titleStyle.isScrollEnable{//等分
coverView.frame.origin.x += titleStyle.coverMargin
coverView.frame.size.width -= titleStyle.coverMargin * 2
}
}
}
}
//
// TGContentView.swift
// TGPageView
//
// Created by targetcloud on 2017/3/22.
// Copyright © 2017年 targetcloud. All rights reserved.
//
import UIKit
private let kContentCellID = "kContentCellID"
//MARK:- delegate 11
protocol TGContentViewDelegate :class {
func contentView(_ contentView:TGContentView,didEndScroll inIndex : Int)
func contentView(_ contentView:TGContentView,sourceIndex : Int,targetIndex : Int,progress : CGFloat)
}
class TGContentView: UIView {
//MARK:- delegate 22
weak var delegate : TGContentViewDelegate?
fileprivate var childVCs : [UIViewController]
fileprivate var parentVC : UIViewController
fileprivate var fromOffsetX : CGFloat = 0
fileprivate var isTitleClickForbidSVDelegate : Bool = false
fileprivate lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = self.bounds.size;
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: self.bounds, collectionViewLayout: layout)//闭包中用到当前对象中的所有属性不能省略self.
cv.dataSource = self
cv.delegate = self
cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: kContentCellID)
cv.isPagingEnabled = true
cv.showsHorizontalScrollIndicator = false
// cv.bounces = false
cv.scrollsToTop = false//点击状态栏不要回到顶部
return cv
}()
init(frame: CGRect,childVCs : [UIViewController],parentVC : UIViewController) {
self.childVCs = childVCs
self.parentVC = parentVC
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension TGContentView{
fileprivate func setupUI(){
for childVC in childVCs{
parentVC.addChildViewController(childVC)
}
addSubview(collectionView)
}
}
extension TGContentView : UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return childVCs.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kContentCellID, for: indexPath)
// cell.backgroundColor = UIColor.randomColor()
for subview in cell.contentView.subviews{
subview.removeFromSuperview()
}
let childVC = childVCs[indexPath.item]
cell.contentView.addSubview(childVC.view)
return cell
}
}
extension TGContentView : UICollectionViewDelegate{
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollDidEndScroll()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
scrollDidEndScroll()
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
//记录开始拖动的offset
fromOffsetX = scrollView.contentOffset.x
isTitleClickForbidSVDelegate = false
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//MARK:- delegate 33
//点击标题会调用在TGContentView.swift中的TGTitleViewDelegate实现的代理方法func titleView(_ titleView: TGTitleView, targetIndex: Int),此时这个 func中的代码collectionView.scrollToItem又会调用scrollViewDidScroll
//这时需要禁止其调用scrollViewDidScroll
// guard !isTitleClickForbidSVDelegate else {
// return
// }
let toOffsetX = scrollView.contentOffset.x
guard !isTitleClickForbidSVDelegate && toOffsetX != fromOffsetX else {
return
}
var sourceIndex = 0
var targetIndex = 0
var progress : CGFloat = 0
let collectionViewWidth = collectionView.bounds.width
if toOffsetX > fromOffsetX {
//左划向右滚 (回弹偶尔会调用右划向左滚,所以越界都要判断)
sourceIndex = Int(toOffsetX / collectionViewWidth)
targetIndex = sourceIndex + 1
if targetIndex >= childVCs.count{
targetIndex = childVCs.count - 1
print(" --- \(toOffsetX) 到尾了--- ");
}
progress = (toOffsetX - fromOffsetX) / collectionViewWidth
if progress > 1 {
progress = 1
}
if toOffsetX - fromOffsetX >= collectionViewWidth{
targetIndex = sourceIndex
}
print("<<<<<<左划向右滚 -> sourceIndex:\(sourceIndex) -> targetIndex:\(targetIndex) progress:\(progress)")
delegate?.contentView(self, sourceIndex: sourceIndex, targetIndex: targetIndex, progress: progress)
}else{
//右划向左滚(回调偶尔会调用左划向右滚)
if toOffsetX<0 {
print(" --- \(toOffsetX) 到头了--- ");
return
}
targetIndex = Int(toOffsetX / collectionViewWidth)
sourceIndex = targetIndex + 1
if sourceIndex >= childVCs.count{//
sourceIndex = childVCs.count - 1
}
progress = (fromOffsetX - toOffsetX) / collectionViewWidth
if progress > 1 {
progress = 1
}
//完全划过去
if fromOffsetX - toOffsetX >= collectionViewWidth {
sourceIndex = targetIndex
}
print(">>>>>>右划向左滚 <- targetIndex:\(targetIndex) <- sourceIndex:\(sourceIndex) progress:\(progress)")
delegate?.contentView(self, sourceIndex: sourceIndex, targetIndex: targetIndex, progress: progress)
}
}
private func scrollDidEndScroll(){
//MARK:- delegate 33
let index = Int( collectionView.contentOffset.x / collectionView.bounds.width )
delegate?.contentView(self, didEndScroll: index)
}
}
//MARK:- 代理 2
extension TGContentView:TGTitleViewDelegate{
func titleView(_ titleView: TGTitleView, targetIndex: Int) {
isTitleClickForbidSVDelegate = true
let indexPath = IndexPath(item: targetIndex, section: 0)
collectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: false)
}
}