现在好多app(喜马拉雅,新浪微博等)都有类似UIPageViewController的效果,实际开发中我们要进行特殊效果,需要自定义,我写了一个swift版本的.效果如下
点击链接下载 github下载地址
- 多标题,可以滑动
- 标题较少,上面不滚动,距离等分
这种效果有很多坑点,比如,快速连续滑动时候流畅性问题,字体颜色渐变色效果,标题尽量滑到居中位置,我要起始在非第一个控制器上等等.下面我把关键代码贴出来.
UI架构
我用的是父子控制器, parentVc.addChildViewController(vc),子控制器的视图放在父控制器的collectionView上显示.默认样式我用了一个JDPageStyle类专门进行处理.
标题较多时用 style.isScrollEnable = true 来让上面的标题可以滚动,标题较少时style.isScrollEnable = false上面距离等分.
上面用scrollView 下面用collectionView,互为对方的代理,对方代理传过来后自己滑动,自己滑动发送代理对方滚动,对方传来滑动消息自己滑动时,不发送代理.
fileprivate func setupUI() {
// 1.将childVc添加到父控制器中
for vc in childVcs {
parentVc.addChildViewController(vc)
}
// 2.初始化用于显示子控制器View的View(UIScrollView/UICollectionView)
addSubview(collectionView)
}
//设置contentView&titleView关系
titleView?.delegate = contentView
contentView?.delegate = titleView
- 设置起始index时要用多线程,主线程异步,这样初始化完毕后才执行这个滚动方法.
DispatchQueue.main.async {
let indexPa = IndexPath(item: self.startIndex, section: 0)
self.contentView?.collectionView.scrollToItem(at: indexPa, at: .centeredHorizontally, animated: false)
self.contentView?.delegate?.contentView((self.contentView)!, targetIndex: self.startIndex, progress: 1)
self.contentView?.delegate?.contentView( (self.contentView)!, endScroll: self.startIndex)
}
- 按钮滚动到尽量居中位置
标签滑动中心位置大于屏幕一半时候,如果scrollView足够长,scrollview滑动,使标签居中
var offsetX = newLabel.center.x - scrollView.bounds.width * 0.5
if offsetX < 0 {
offsetX = 0
}
//滑动到后面,scrollview不能再滑动时候,不滑动
let maxOffset = scrollView.contentSize.width - bounds.width
if offsetX > maxOffset {
offsetX = maxOffset
}
scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
下面控制器滑动时候
在scrollViewDidScroll里面判断左滑右滑,并判断进度,把信息传递给上面.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == startOffsetX || isForbidDelegate { return }
// 1.定义目标的index、进度
var targetIndex : Int = 0
var progress : CGFloat = 0
// 2.判断用户是左滑还是右滑
if scrollView.contentOffset.x > startOffsetX { // 左滑 右移动
targetIndex = Int(startOffsetX / scrollView.bounds.width) + 1
if targetIndex >= childVcs.count {
targetIndex = childVcs.count - 1
}
progress = (scrollView.contentOffset.x - startOffsetX) / scrollView.bounds.width
} else { // 右滑
targetIndex = Int(startOffsetX / scrollView.bounds.width) - 1
if targetIndex < 0 {
targetIndex = 0
}
progress = (startOffsetX - scrollView.contentOffset.x) / scrollView.bounds.width
}
// 3.将数据传递给titleView
delegate?.contentView(self, targetIndex: targetIndex, progress: progress)
}}
快速滑动细节处理 如果开始拖拽时候下面view一页没滑动完毕,正在滑动,应计算他应该在哪里结束,让它滑动到它应该结束的地方,以此为其实值,要不会有上面标题底部条显示错乱
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// 记录开始的位置
isForbidDelegate = false
if Int(collectionView.contentOffset.x) % Int(collectionView.bounds.width) != 0 {
let theNum = collectionView.contentOffset.x / collectionView.bounds.width
let res = self.numFormat(num: Float(theNum ), format: "0")
collectionView.scrollToItem(at: IndexPath(item: Int(res)!, section: 0), at: .left, animated: false)
startOffsetX = collectionView.bounds.width*CGFloat(Float(res)!)
delegate?.contentView(self, targetIndex: Int(res)!, progress: 1)
delegate?.contentView(self, endScroll: Int(res)!)
}else{
startOffsetX = scrollView.contentOffset.x
}
}
文字颜色渐变处理
文字颜色不能有透明度,要可以转成纯的rgb颜色才可以
在上面view里面接受到下面正在滑动中的progress时候,根据新旧两个index,取出两个头部label,设置颜色字体等
func contentView(_ contentView: JDContentView, targetIndex: Int, progress: CGFloat) {
if progress < 1 {
self.isUserInteractionEnabled = false
}else{
self.isUserInteractionEnabled = true
}
// 1.取出两个Label
let oldLabel = titleLabels[currentIndex]
let newLabel = titleLabels[targetIndex]
// 2.渐变文字颜色
let selectRGB = getGRBValue(style.selectColor)
let normalRGB = getGRBValue(style.normalColor)
let deltaRGB = (selectRGB.0 - normalRGB.0, selectRGB.1 - normalRGB.1, selectRGB.2 - normalRGB.2)
oldLabel.textColor = UIColor(r: selectRGB.0 - deltaRGB.0 * progress, g: selectRGB.1 - deltaRGB.1 * progress, b: selectRGB.2 - deltaRGB.2 * progress)
newLabel.textColor = UIColor(r: normalRGB.0 + deltaRGB.0 * progress, g: normalRGB.1 + deltaRGB.1 * progress, b: normalRGB.2 + deltaRGB.2 * progress)
if progress > 0.5 {
oldLabel.font = style.titleFont
newLabel.font = style.selectedTitleFont
}else{
oldLabel.font = style.selectedTitleFont
newLabel.font = style.titleFont
}}
private func getGRBValue(_ color : UIColor) -> (CGFloat, CGFloat, CGFloat) {
guard let components = color.cgColor.components else {
fatalError("文字颜色请按照RGB方式设置 颜色不能有透明度")
}
return (components[0] * 255, components[1] * 255, components[2] * 255)
}