本人刚陆续学习了 iOS 开发一个月左右,因为马上就要做一个简单的 iOS 项目,有个需求是需要实现类似于 Android中 PageView 的功能,而 github 上我又没有找到合适项目可以直接拿来用,因此在参考了别人的一些代码后,自己用 swift 写了一个简单的 TabPageView。
先上预览图
下面来说说原理。其实原理挺简单的,我是将它分成了两个部分,分别是标签栏 TabTitleView 以及页面内容 PageContentView ,然后分别添加 CollectionView
来展示内容。TabTitleView 底部添加一个 UIView
作为滚动条, PageContentView 中的 CollectionView
开启分页, 基本上内容的展示就没有问题了,重要的是怎么让它们联动。
- TabTitleView 和 PageContentView 均继承
UICollectionViewDelegate
,并且分别在init
方法中接收一个闭包 callback,用于回调更新UI。 - TabTitleView 重写
collectionView(_: UICollectionView, didSelectItemAt: IndexPath)
方法,在里面调用 callback 更新 PageContentView ,实现setOffset(offsetRatio: CGFloat)
方法用于回调。 - PageContentView 重写
scrollViewDidScroll(_: UIScrollView)
,在里面调用 callback 更新 TabTitleView,实现setPage(index: Int)
方法用于回调。 - 在 View 的初始化过程中,接收 controllers,这些 controllers 的 view 作为 PageContentView 展示的内容。初始化 TabTitleView,传入一个 callback,调用 PageContentView 的
setPage(index: Int)
方法;初始化 PageContentView,传入一个 callback,调用 TabTitleView 的setOffset(offsetRatio: CGFloat)
方法。
联动的基本实现方式就是这样了,接下来看看代码。
TabPageViewController.swift
public func initView(titleList: [TabTitleModel], controllers: [UIViewController], contentView: UIView, configuration: TabPageViewConfiguration){
var pageViewList = Array()
if controllers.count > 0 {
for controller in controllers{
pageViewList.append(controller.view)
addChildViewController(controller)
controllerList.append(controller)
}
}
tabTitleView = TabTitleView(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: configuration.titleHeight), titleList:
titleList, conf: configuration.tabTitleConf, callback:{
(index) in
self.pageContentView?.setPage(index: index)
})
contentView.addSubview(tabTitleView!)
pageContentView = PageContentView(frame: CGRect(x: 0, y: configuration.titleHeight, width: contentView.frame.width, height: contentView.frame.height - configuration.titleHeight), pageViewList: pageViewList, conf: configuration.pageViewConf, callback:{
(offset) in
self.tabTitleView?.setOffset(offsetRatio: offset)
})
contentView.addSubview(pageContentView!)
}
TabTitleView.swift
// MARK: methods
func setOffset(offsetRatio: CGFloat){
var offsetX = CGFloat()
offsetX = 0
let index = Int(offsetRatio)
if index > 0{
for i in 0...index - 1{
offsetX += titleViewList![i].bounds.width
offsetX += collectionLayout!.minimumInteritemSpacing
}
}
offsetX += (titleViewList![index].bounds.width + collectionLayout!.minimumInteritemSpacing) * (offsetRatio - CGFloat(index))
UIView.animate(withDuration: 0.0, animations: {
var width: CGFloat
if index < self.titleViewList!.count - 1{
width = self.titleViewList![index].frame.width + (self.titleViewList![index + 1].frame.width - self.titleViewList![index].frame.width) * (offsetRatio - CGFloat(index))
}else{
width = self.titleViewList![index].frame.width
}
self.scrollBar!.frame = CGRect(x:offsetX, y:self.scrollBar!.frame.origin.y, width: width, height:self.scrollBar!.frame.height)
})
currentIndex = index
}
func setIndex(index: Int){
var offsetX = CGFloat()
offsetX = 0
if let viewList = titleViewList, index > 0{
for index in 0...index - 1{
offsetX += viewList[index].bounds.width
offsetX += collectionLayout!.minimumInteritemSpacing
}
}
UIView.animate(withDuration: 0.3, animations: {
self.scrollBar!.frame = CGRect(x:offsetX, y:self.scrollBar!.frame.origin.y, width:self.titleViewList![index].frame.width, height:self.scrollBar!.frame.height)
})
currentIndex = index
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if currentIndex != indexPath.row {
setIndex(index: indexPath.row)
callback!(indexPath.row)
currentIndex = indexPath.row
}
}
PageContentView.swift
// MARK: delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if collectionView!.contentOffset.x == 0{
callback?(0)
}else if doCallbackInvock{
let offsetRatio = collectionView!.contentOffset.x / self.frame.width
callback?(offsetRatio)
}
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
doCallbackInvock = true
}
// MARK: methods
func setPage(index: Int){
let point = CGPoint(x: CGFloat(index) * collectionView!.frame.size.width, y:collectionView!.frame.origin.y)
doCallbackInvock = false
collectionView?.setContentOffset(point, animated: true)
currentIndex = index
}
调用该 TabPageView 的方式是,先让 controller 继承 TabPageViewController,然后像这样调用initView
方法
let firstController = UIStoryboard.init(name: "FirstFragment", bundle: nil).instantiateViewController(withIdentifier: "FirstFragment")
let secondController = UIStoryboard.init(name: "SecondFragment", bundle: nil).instantiateViewController(withIdentifier: "SecondFragment")
let taskController = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Task")
let image = UIImage(named: "logo")
let titleList: [TabTitleModel] = [TabTitleModel("aaa", image!), TabTitleModel("bbbbbbbbb", image!), TabTitleModel("cccc", image!)]
var conf = TabPageViewConfiguration()
initView(titleList: titleList, controllers: [firstController, secondController, taskController], contentView: contentView, configuration: conf)
实现起来还是比较简单的,也基本满足了我新项目的需求。
顺便把这个项目的 github 地址也贴上:https://github.com/beetlebum233/iOS-TabPageView
如果有更好的实现思路或者有可以优化的地方,可以随时告诉我,十分感谢!