学习Swift之后这个控件写了很久了. 但是把这个框架做成pod集成到项目中的时候发现因为控件的核心类没有声明"public"而导致无法访问. 在把class 前声明为Public后又有一堆协议方法也需要修改访问权限..(这个比OC有点麻烦了)
顺便整理记录一下这个框架吧
git: [https://github.com/Zafirzzf/ZFPageTitleView]
/// 构造函数
///
/// - Parameters:
/// - frame: 包含自控制器的整个frame
/// - titles: 标题数组
/// - childControllers: 内容控制器数组
/// - parentVC: 装此控件的控制器
/// - style: 界面主题色
public init(frame: CGRect, titles: [String], childControllers: [UIViewController], parentVC: UIViewController,style: ZFPageStyle = ZFPageStyle()) {
self.titleStyle = style
self.titles = titles
self.childControllers = childControllers
self.parentVC = parentVC
parentVC.automaticallyAdjustsScrollViewInsets = false
super.init(frame: frame)
setupUI()
}
在接收到最需要的标题数组和子控制器数组之后,创建标题视图,并用collectionView展示子控制器里的view.
fileprivate func setupUI() {
// title
let titleView = ZFTitleView(frame: CGRect(x: 0, y: 0, width: bounds.width,
height: titleStyle.titleViewHeight),
style: titleStyle,
titles: titles)
addSubview(titleView)
//内容
let contentFrame = CGRect(x: 0, y: titleView.frame.maxY, width: bounds.width, height: bounds.height - titleView.frame.height)
let contentView = ZFContentView(frame:contentFrame,
childControllers: childControllers,
parentVC: parentVC)
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(contentView)
//设置标题view和内容视图的关系
titleView.delegage = contentView
contentView.delegate = titleView
}
ZFTitleView
与ZFContentView
是抽离出来的两个类.分别控制标题视图与内容.titleView于contentView之间的事件交互利用代理完成很方便.
TitleView上根据标题的数量创建N个Button添加到scrollView上,根据标题的总长度调整位置即可.
在滑动下方contentView的时候上方标题视图中的当前选中文字与将要选中的文字有一个渐变的颜色变化.处理方式如下:
public func contentView(_ contentView: ZFContentView, targetIndex: Int, progress: CGFloat) {
if progress > 0.9 {
selectIndex = targetIndex
//调整位置
setScrollInset(titleLabels[selectIndex])
}
let fonttransformScale = style.fontTransformScale
let oldLabelScale = fonttransformScale - (fonttransformScale - 1) * progress
let targetLabelScale = 1 + (fonttransformScale - 1) * progress
let oldLabel = titleLabels[selectIndex]
let newLabel = titleLabels[targetIndex]
// 滑动过程中缩放字体
oldLabel.transformScale(scale: oldLabelScale)
newLabel.transformScale(scale: targetLabelScale)
// 改变字体颜色
let normalColor = getRGBValue(style.textNormalColor)
let selectColor = getRGBValue(style.textSelectColor)
let excessColor = (selectColor.0 - normalColor.0,
selectColor.1 - normalColor.1,
selectColor.2 - normalColor.2)
oldLabel.textColor = UIColor(red: (selectColor.0 - excessColor.0 * progress) / 255.0,
green: (selectColor.1 - excessColor.1 * progress) / 255.0,
blue: (selectColor.2 - excessColor.2 * progress) / 255.0,
alpha: 1)
newLabel.textColor = UIColor(red: (normalColor.0 + excessColor.0 * progress) / 255.0,
green: (normalColor.1 + excessColor.1 * progress) / 255.0,
blue: (normalColor.2 + excessColor.2 * progress) / 255.0,
alpha: 1)
}
func getRGBValue(_ color: UIColor) ->(CGFloat,CGFloat,CGFloat) {
guard let components = color.cgColor.components else {
fatalError("文字颜色请按照RGB设置")
}
return (components[0] * 255, components[1] * 255, components[2] * 255)
}
核心步骤是:
- 获取normalcolor和selectcolor的两个RGB值.
- 两个值的R,G,B相减获取到过度的一个颜色值.
- 根据当前滑动的进度progress,使normalcolor和selectcolor向过度值进行过度
注: 这种方法要确保Color.cgColor.components有值,所以两种颜色需要通过RGB进行设置.其它方法设置的会获取不到RGB值.
如果想要加上滑动过程中sel的标题进行缩放,同理根据progress操作.
contentView里有一个左右滑动的collectionView,它的cell就是childControllers里的每个控制器的view.这样在控制器比较多的情况下可以让view之间复用.比ScrollView会节省内存的占用.
需要注意的是为了防止复用.每次加载新view时要将之前的删除.
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath)
// 注意删除之前cell上的view
_ = cell.contentView.subviews.map{ $0.removeFromSuperview() }
let childView = childVC[indexPath.item].view!
childView.frame = cell.contentView.bounds
cell.contentView.addSubview(childView)
return cell
}
滑动contentView更新titleView的变化通过scrollView的代理即可.
//MARK:- ScrollView 代理
extension ZFContentView: UICollectionViewDelegate {
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
delegate?.contentView(self, endScroll:Int(scrollView.contentOffset.x / bounds.width))
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if isForbidDelegate { return }
let offsetX = scrollView.contentOffset.x
var targetIndex: Int
var progress: CGFloat = 0
if offsetX < scrollStartOffSet { //向左滑
progress = (scrollStartOffSet - offsetX) / bounds.width
targetIndex = Int(scrollStartOffSet / bounds.width) - 1
if targetIndex < 0{
targetIndex = 0
}
}else {
progress = (offsetX - scrollStartOffSet) / bounds.width
targetIndex = Int(scrollStartOffSet / bounds.width) + 1
if targetIndex >= childVC.count {
targetIndex = childVC.count - 1
}
}
delegate?.contentView(self, targetIndex: targetIndex, progress: progress)
}
}