一年半开发经验,使用 RxSwift 构建一个项目的基本框架,这
如果你跟我一样,工作了一年半,相信你也经历过几个大版本的迭代开发,甚至拥有几个应用的开发经验。那么在实现过那么多复杂的业务逻辑之后,你觉得,利用纯代码,怎么样去创建一个美、优、简以及便于管理的基本框架呢? 这两天在思考这个问题,于是有了以下想法(可能还不是很成熟,如果你有更好的想法,请评论告知我,在此提前感谢)。
撸撸思路
今天以搭建一个项目的基本框架为例,来谈谈这个问题,最终的效果图如下:
显示效果大家可以看到,这确实是一个复杂项目的基础,即使逻辑再复杂的界面,最开始也都是从这个界面起步的,而对于它,我们需要做的:
- 初始化几个子控制器
- 添加自定义的 TabBarView
- 添加自定义的 NavigationBar
- 添加自定的 TopBarColumnView
准备工作
这里主要是准备一些项目开发需要的资源和添加一些需要使用到的第三方库,过程简单、琐碎:
- 导入需要使用到的全部图片资源
- 导入需要使用到的第三方: RxSwift、RxCocoa
- 结合 UIStoryBoard 初始化几个控制器,并在每个控制器上添加初始化显示图片
完成上述操作之后,可得项目结构如下:
目录名称 | 功能介绍 |
---|---|
Main | 项目的主要入口 |
Main - MenuViewController | 菜单控制器,作用与 TabBarController 相同 |
Business | 存储所有的业务逻辑模块 |
Business - Home | 首页业务模块 |
Business - Medium | 书影音 业务模块 |
Business - Broadcast | 广播 业务模块 |
Business - Group | 小组 业务模块 |
Business - Profile | 我的 业务模块 |
Utility | 存储所有的功能模块 |
Utility - Extension | 存储所有功能类的功能扩展 |
当然,大家觉得无聊就直接跳过这个操作,在 github 上 https://github.com/iJudson/RxBasicInterface.git <前期准备> 该 commit 上直接拿到这一步的代码
正题
结束了所有的准备工作,接下来进入正题:使用 RxSwift 创建 TabBarView 供菜单控制器 MenuViewController 使用
TabBarView 的创建
其实对于某些 View 的使用,即使是自己创建的,往往会在过了一段较长的时间之后,再去使用这张 View 时,竟然发现无从下手,需要再重温下代码,特别是对于那些特别复杂的 View,有多个便利构造函数,或者特别多的子控件,而且需要根据不同情况去使用其中的某些控件...
还有一种情况, 你在公司与其他人协同工作时,会不会发现要去使用到他创建的较为复杂的 View 时,需要或一些时间,感到特别烦呢?
针对这两个问题,我的解决方案是:
<创建该 View 专属的样式类>
在这里,你可以先去跟你的同事或跟自己约定一套创建 View 的风格,就是在创建该 View 的时候先去创建该 View 的专属样式类:用于管理该 View 的所有样式:颜色、子控件类型和数量、子控件的显示位置...
我们想创建 TabBarView,让我们先来创建 TabBarView 的样式管理类:
typealias TabBarItemElement = (normalImage: UIImage?, selectedImage: UIImage?, title: String?)
// TabBarView 的样式管理类
struct TabBarStyle {
// 子控件的元素集合
var itemElements: [TabBarItemElement]
// TabBarView 的颜色主题(默认为白色)
var themeColor: UIColor = UIColor.white
// 是否含有顶部分割线
var hasTopLine: Bool = true
// 在初始化构造器中,只去初始化必要的元素,避免初始化构造器过于冗长
init(elements: [TabBarItemElement] = []) {
self.itemElements = elements
}
}
<创建 TabBarView>
有了上面的样式管理类,我们在该 View 中,对外的属性就特别少,基本就只有两个,这也大程度的增强了该 View 的封装性。
1. 定义对外的样式属性:
// 主题样式
var themeStyle: TabBarStyle = TabBarStyle() {
didSet {
// 通过属性监视器,外部可以通过该属性更新 TabBarView 的整体样式
update(ThemeStyle: themeStyle)
}
}
2. 创建便利构造函数:便于外部对该类的使用
convenience init(frame: CGRect? = nil, themeStyle: TabBarStyle) {
// 当外部不传入 Frame 值时,我们默认使用以下 Frame 值
let barFrame = frame ?? CGRect(x: 0, y: screenHeight - tabBarHeight, width: screenWidth, height: tabBarHeight)
self.init(frame: barFrame)
// 设置背景颜色
self.backgroundColor = themeStyle.themeColor
// 根据样式管理类,更新该 view 的主体样式
update(ThemeStyle: themeStyle)
// 根据样式管理类,决定是否添加顶部分割线
add(TopLine: themeStyle.hasTopLine)
}
OK,到这里这个 View 大体上就创建完毕了。是不是很简单,日后是不是也很便于管理?目前来看,这个 View 只有一个对外属性,还有一个 convenience 构造函数,确实很便于我们管理,而对于样式,我们只需要去查看样式管理类,就只有这个 View 的所有的样式...
哈,其实接下来,还是有些细节要继续完善。
3. 实现样式更新的方法
在其中,我们只需要拿到外部传进来的样式属性,去更新样式。
因为这里的 TabBarItem 既有图片,又有标题,所有外部需要将这两个属性都传进来。
fileprivate func update(ThemeStyle style: TabBarStyle) {
// 所有的样式元素数量
let itemCount = style.itemElements.count
let itemWidth = screenWidth/itemCount
var commonItemFrame = CGRect(x: 0, y: 0, width: itemWidth, height: tabBarHeight)
// 通过 for 循环,将拿到的所有元素遍历添加到 TabBarView 上
for (index, element) in style.itemElements.enumerated() {
commonItemFrame.origin.x = index * itemWidth
let commonTabBarIem = CommonItemView(frame: commonItemFrame, image: element.normalImage, title: element.title)
commonTabBarIem.setImage(element.selectedImage, for: .selected)
commonTabBarIem.alignmentStyle = .upImageDownText(space: 2)
commonTabBarIem.tag = index
commonTabBarIem.titleLabel?.font = UIFont.systemFont(ofSize: 12)
commonTabBarIem.setTitleColor(UIColor.gray, for: .normal)
commonTabBarIem.setTitleColor(UIColor.black, for: .selected)
self.addSubview(commonTabBarIem)
// 这里我们添加监听者,监听每一个 Item 的点击操作,当 item 被点击时,我们通过 Driver 序列,发送点击事情到这和序列上
let selectedIndex = commonTabBarIem.rx.controlEvent(.touchDown).throttle(0.5, scheduler: MainScheduler.instance).flatMapLatest { (_) -> Driver<Int> in
return Driver<Int>.just(commonTabBarIem.tag)
}.asDriver(onErrorJustReturn: 0)
// 对外监听属性的集合
selectedIndexes.append(selectedIndex)
// 默认选中第一个 tabBarItem
commonTabBarIem.isSelected = (index == 0 ? true : false)
}
}
在上面方法中,大家应该有注意到,我们需要一个对外监听属性的集合,监听用户的点击操作,因为一般 TabBar 有5个 Item,故而这个集合一般都有五个对外监听属性,属性定义如下:
//当前选中的 index
var selectedIndexes: [Driver<Int>] = []
4. 添加顶部分割线
fileprivate func add(TopLine isExisting: Bool) {
// 事先判断是否需要添加分割线,如果不需要,可直接 return 返回
guard isExisting else {
return
}
let lineHeight: CGFloat = 0.5
let topLine = UIView(frame: CGRect(x: 0, y: -lineHeight, width: screenWidth, height: lineHeight))
topLine.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
self.addSubview(topLine)
}
到这一步的代码,大家可以在 https://github.com/iJudson/RxBasicInterface <创建 TabBarView> 当前 commit 上获取(注意,现在在这个 commit 上运行程序,还是没有任何效果,毕竟还没有使用到该 TabBarView)
TabBarView 在 MenuViewController 的使用
TabBarView 是在 MenuViewController 去使用的, 在 MenuViewController 中也需要去监听 TabBarView 的 selectedIndexes 数组属性,从而监听到 TabBarView 上的点击操作。而实际上,TabBarView 的使用,由于使用了 RxSwift ,也变得特别简单,也方便管理。
1. 我们在 viewDidLoad 添加两个方法
- 初始化 MenuViewController 中所有的子控制器,并默认让首页控制器显示在主页上
- 配置底部 TabBarView ,创建所需样式管理类 TabBarViewStyle,并添加所需元素
override func viewDidLoad() {
super.viewDidLoad()
initializeTopViewControllers()
configureTabBar()
}
接下来,我们只需要定义并完善上述两个方法...
2. 初始化所有的子控制器
fileprivate func initializeTopViewControllers() {
// 通过 UIStoryboard 拿到所有的子导航控制器
let homeNav = UIStoryboard.NavigationController.home
let mediumNav = UIStoryboard.NavigationController.medium
let broadcastNav = UIStoryboard.NavigationController.broadcast
let groupNav = UIStoryboard.NavigationController.group
let profileNav = UIStoryboard.NavigationController.profile
// 并预先添加到 topViewControllers 数组中,当点击某一个 TabBarItem 时,根据需要添加具体的控制器
topViewControllers = [homeNav, mediumNav, broadcastNav, groupNav, profileNav]
// 初始化时,默认添加首页控制器
self.addChildViewController(homeNav)
homeNav.view.frame = self.view.bounds
self.view.addSubview(homeNav.view)
// 默认显示首页控制器
self.selectedViewController = homeNav
// 更新状态栏的状态
self.setNeedsStatusBarAppearanceUpdate()
}
3. 配置 TabBarView,监听其中的点击属性
- 我们拿到所有的图片和标题属性
- 配置 TabBar 样式管理类
- 监听TabBarItem 的点击操作(这里由于 RxSwift 会变得特别简单)
- 处理 TabBarItem 的点击事件
fileprivate func configureTabBar() {
// 拿到五个 BarItem 上的元素(图片和标题)
let barItemElements: [TabBarItemElement] = [
(normalImage: UIImage(named: "ic_tab_home_gray_32x32_"), selectedImage: UIImage(named: "ic_tab_home_32x32_"), title: "首页"),
(normalImage: UIImage(named: "ic_tab_subject_gray_32x32_"), selectedImage: UIImage(named: "ic_tab_subject_32x32_"), title: "书影音"),
(normalImage: UIImage(named: "ic_tab_timeline_gray_32x32_"), selectedImage: UIImage(named: "ic_tab_timeline_32x32_"), title: "广播"),
(normalImage: UIImage(named: "ic_tab_group_gray_32x32_"), selectedImage: UIImage(named: "ic_tab_group_32x32_"), title: "小组"),
(normalImage: UIImage(named: "profile_normal_32x32_"), selectedImage: UIImage(named: "profile_active_32x32_"), title: "我的")
]
// 创建管理样式类
let tabBarStyle = TabBarStyle(elements: barItemElements)
// 拿到样式类,创建 TabBarView
let tabBarView = TabBarView(themeStyle: tabBarStyle)
self.view.addSubview(tabBarView)
// 监听 TabBarView 的 selectedIndexes 属性,当有点击操作时,触发处理点击事件的方法
for selectedIndex in tabBarView.selectedIndexes {
selectedIndex.drive(onNext: { [weak self] (index) in
guard let strongSelf = self else {
return
}
// 处理点击事件
strongSelf.handleTopControllerSelectionEvent(currentIndex: index)
}).disposed(by: disposeBag)
}
}
而其点击事件的处理如下:
fileprivate func handleTopControllerSelectionEvent(currentIndex: Int) {
// 移除上一次选择的控制器
if let selectedViewController = selectedViewController {
selectedViewController.willMove(toParentViewController: nil)
selectedViewController.view.removeFromSuperview()
selectedViewController.removeFromParentViewController()
selectedViewController.viewWillDisappear(false)
}
// 添加当前选择的控制器,并显示
let currentSelectedViewController = topViewControllers[currentIndex]
self.addChildViewController(currentSelectedViewController)
currentSelectedViewController.view.frame = self.view.bounds
self.view.addSubview(currentSelectedViewController.view)
currentSelectedViewController.didMove(toParentViewController: self)
self.selectedViewController = currentSelectedViewController
self.setNeedsStatusBarAppearanceUpdate()
self.selectedIndex = currentIndex
// 置顶 TabBar
for subView in self.view.subviews {
if let tabBar = subView as? TabBarView {
self.view.bringSubview(toFront: tabBar)
}
}
}
OK,到这里,我们一个简单的项目框架就已经完成,其效果如下:
BasicInterface.gif你可以在 https://github.com/iJudson/RxBasicInterface <TabBarView 在 MenuViewController 的使用> 的分支上拿到迄今为止的代码。
后续
当然一个简单的项目框架还不止这些可能会有:
- 导航栏 - NavigationBar
- 顶部选择栏 - TopBarColumnView
而这两个 View 的创建跟 TabBarView 的创建是一模一样的,大家都试着去创建体会下。我这里也不再赘余,而最终的效果如下:
Last Version BasicInterface.gif最终的代码,大家可以在 https://github.com/iJudson/RxBasicInterface 中找到。
如果这个基本框架还有哪些地方可以继续优化,请不吝评论告知我。
如果你觉得这个基本框架还不错,欢迎 star。
感谢!