如果你跟我一樣闽撤,工作了一年半,相信你也經(jīng)歷過幾個大版本的迭代開發(fā)脯颜,甚至擁有幾個應(yīng)用的開發(fā)經(jīng)驗哟旗。那么在實現(xiàn)過那么多復(fù)雜的業(yè)務(wù)邏輯之后,你覺得栋操,利用純代碼闸餐,怎么樣去創(chuàng)建一個美、優(yōu)讼庇、簡以及便于管理的基本框架呢绎巨? 這兩天在思考這個問題近尚,于是有了以下想法(可能還不是很成熟蠕啄,如果你有更好的想法,請評論告知我,在此提前感謝)歼跟。
擼擼思路
今天以搭建一個項目的基本框架為例和媳,來談?wù)勥@個問題,最終的效果圖如下:
大家可以看到哈街,這確實是一個復(fù)雜項目的基礎(chǔ)留瞳,即使邏輯再復(fù)雜的界面,最開始也都是從這個界面起步的骚秦,而對于它她倘,我們需要做的:
- 初始化幾個子控制器
- 添加自定義的 TabBarView
- 添加自定義的 NavigationBar
- 添加自定的 TopBarColumnView
準(zhǔn)備工作
這里主要是準(zhǔn)備一些項目開發(fā)需要的資源和添加一些需要使用到的第三方庫,過程簡單作箍、瑣碎:
- 導(dǎo)入需要使用到的全部圖片資源
- 導(dǎo)入需要使用到的第三方: RxSwift硬梁、RxCocoa
- 結(jié)合 UIStoryBoard 初始化幾個控制器,并在每個控制器上添加初始化顯示圖片
完成上述操作之后胞得,可得項目結(jié)構(gòu)如下:
目錄名稱 | 功能介紹 |
---|---|
Main | 項目的主要入口 |
Main - MenuViewController | 菜單控制器荧止,作用與 TabBarController 相同 |
Business | 存儲所有的業(yè)務(wù)邏輯模塊 |
Business - Home | 首頁業(yè)務(wù)模塊 |
Business - Medium | 書影音 業(yè)務(wù)模塊 |
Business - Broadcast | 廣播 業(yè)務(wù)模塊 |
Business - Group | 小組 業(yè)務(wù)模塊 |
Business - Profile | 我的 業(yè)務(wù)模塊 |
Utility | 存儲所有的功能模塊 |
Utility - Extension | 存儲所有功能類的功能擴(kuò)展 |
當(dāng)然,大家覺得無聊就直接跳過這個操作阶剑,在 github 上 https://github.com/iJudson/RxBasicInterface.git <前期準(zhǔn)備> 該 commit 上直接拿到這一步的代碼
正題
結(jié)束了所有的準(zhǔn)備工作跃巡,接下來進(jìn)入正題:使用 RxSwift 創(chuàng)建 TabBarView 供菜單控制器 MenuViewController 使用
TabBarView 的創(chuàng)建
其實對于某些 View 的使用,即使是自己創(chuàng)建的牧愁,往往會在過了一段較長的時間之后素邪,再去使用這張 View 時,竟然發(fā)現(xiàn)無從下手猪半,需要再重溫下代碼娘香,特別是對于那些特別復(fù)雜的 View,有多個便利構(gòu)造函數(shù)办龄,或者特別多的子控件烘绽,而且需要根據(jù)不同情況去使用其中的某些控件...
還有一種情況, 你在公司與其他人協(xié)同工作時俐填,會不會發(fā)現(xiàn)要去使用到他創(chuàng)建的較為復(fù)雜的 View 時安接,需要或一些時間,感到特別煩呢英融?
針對這兩個問題盏檐,我的解決方案是:
<創(chuàng)建該 View 專屬的樣式類>
在這里,你可以先去跟你的同事或跟自己約定一套創(chuàng)建 View 的風(fēng)格驶悟,就是在創(chuàng)建該 View 的時候先去創(chuàng)建該 View 的專屬樣式類:用于管理該 View 的所有樣式:顏色胡野、子控件類型和數(shù)量、子控件的顯示位置...
我們想創(chuàng)建 TabBarView痕鳍,讓我們先來創(chuàng)建 TabBarView 的樣式管理類:
typealias TabBarItemElement = (normalImage: UIImage?, selectedImage: UIImage?, title: String?)
// TabBarView 的樣式管理類
struct TabBarStyle {
// 子控件的元素集合
var itemElements: [TabBarItemElement]
// TabBarView 的顏色主題(默認(rèn)為白色)
var themeColor: UIColor = UIColor.white
// 是否含有頂部分割線
var hasTopLine: Bool = true
// 在初始化構(gòu)造器中硫豆,只去初始化必要的元素龙巨,避免初始化構(gòu)造器過于冗長
init(elements: [TabBarItemElement] = []) {
self.itemElements = elements
}
}
<創(chuàng)建 TabBarView>
有了上面的樣式管理類,我們在該 View 中熊响,對外的屬性就特別少旨别,基本就只有兩個,這也大程度的增強(qiáng)了該 View 的封裝性汗茄。
1. 定義對外的樣式屬性:
// 主題樣式
var themeStyle: TabBarStyle = TabBarStyle() {
didSet {
// 通過屬性監(jiān)視器秸弛,外部可以通過該屬性更新 TabBarView 的整體樣式
update(ThemeStyle: themeStyle)
}
}
2. 創(chuàng)建便利構(gòu)造函數(shù):便于外部對該類的使用
convenience init(frame: CGRect? = nil, themeStyle: TabBarStyle) {
// 當(dāng)外部不傳入 Frame 值時,我們默認(rèn)使用以下 Frame 值
let barFrame = frame ?? CGRect(x: 0, y: screenHeight - tabBarHeight, width: screenWidth, height: tabBarHeight)
self.init(frame: barFrame)
// 設(shè)置背景顏色
self.backgroundColor = themeStyle.themeColor
// 根據(jù)樣式管理類洪碳,更新該 view 的主體樣式
update(ThemeStyle: themeStyle)
// 根據(jù)樣式管理類递览,決定是否添加頂部分割線
add(TopLine: themeStyle.hasTopLine)
}
OK,到這里這個 View 大體上就創(chuàng)建完畢了瞳腌。是不是很簡單非迹,日后是不是也很便于管理?目前來看纯趋,這個 View 只有一個對外屬性憎兽,還有一個 convenience 構(gòu)造函數(shù),確實很便于我們管理吵冒,而對于樣式纯命,我們只需要去查看樣式管理類,就只有這個 View 的所有的樣式...
哈痹栖,其實接下來亿汞,還是有些細(xì)節(jié)要繼續(xù)完善。
3. 實現(xiàn)樣式更新的方法
在其中揪阿,我們只需要拿到外部傳進(jìn)來的樣式屬性疗我,去更新樣式。
因為這里的 TabBarItem 既有圖片南捂,又有標(biāo)題吴裤,所有外部需要將這兩個屬性都傳進(jìn)來。
fileprivate func update(ThemeStyle style: TabBarStyle) {
// 所有的樣式元素數(shù)量
let itemCount = style.itemElements.count
let itemWidth = screenWidth/itemCount
var commonItemFrame = CGRect(x: 0, y: 0, width: itemWidth, height: tabBarHeight)
// 通過 for 循環(huán)溺健,將拿到的所有元素遍歷添加到 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)
// 這里我們添加監(jiān)聽者麦牺,監(jiān)聽每一個 Item 的點擊操作,當(dāng) item 被點擊時鞭缭,我們通過 Driver 序列剖膳,發(fā)送點擊事情到這和序列上
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)
// 對外監(jiān)聽屬性的集合
selectedIndexes.append(selectedIndex)
// 默認(rèn)選中第一個 tabBarItem
commonTabBarIem.isSelected = (index == 0 ? true : false)
}
}
在上面方法中,大家應(yīng)該有注意到岭辣,我們需要一個對外監(jiān)聽屬性的集合吱晒,監(jiān)聽用戶的點擊操作,因為一般 TabBar 有5個 Item沦童,故而這個集合一般都有五個對外監(jiān)聽屬性仑濒,屬性定義如下:
//當(dāng)前選中的 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 <創(chuàng)建 TabBarView> 當(dāng)前 commit 上獲仍ⅰ(注意鹦肿,現(xiàn)在在這個 commit 上運行程序矗烛,還是沒有任何效果,畢竟還沒有使用到該 TabBarView)
TabBarView 在 MenuViewController 的使用
TabBarView 是在 MenuViewController 去使用的箩溃, 在 MenuViewController 中也需要去監(jiān)聽 TabBarView 的 selectedIndexes 數(shù)組屬性瞭吃,從而監(jiān)聽到 TabBarView 上的點擊操作。而實際上涣旨,TabBarView 的使用歪架,由于使用了 RxSwift ,也變得特別簡單霹陡,也方便管理和蚪。
1. 我們在 viewDidLoad 添加兩個方法
- 初始化 MenuViewController 中所有的子控制器,并默認(rèn)讓首頁控制器顯示在主頁上
- 配置底部 TabBarView 烹棉,創(chuàng)建所需樣式管理類 TabBarViewStyle攒霹,并添加所需元素
override func viewDidLoad() {
super.viewDidLoad()
initializeTopViewControllers()
configureTabBar()
}
接下來,我們只需要定義并完善上述兩個方法...
2. 初始化所有的子控制器
fileprivate func initializeTopViewControllers() {
// 通過 UIStoryboard 拿到所有的子導(dǎo)航控制器
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
// 并預(yù)先添加到 topViewControllers 數(shù)組中浆洗,當(dāng)點擊某一個 TabBarItem 時催束,根據(jù)需要添加具體的控制器
topViewControllers = [homeNav, mediumNav, broadcastNav, groupNav, profileNav]
// 初始化時,默認(rèn)添加首頁控制器
self.addChildViewController(homeNav)
homeNav.view.frame = self.view.bounds
self.view.addSubview(homeNav.view)
// 默認(rèn)顯示首頁控制器
self.selectedViewController = homeNav
// 更新狀態(tài)欄的狀態(tài)
self.setNeedsStatusBarAppearanceUpdate()
}
3. 配置 TabBarView伏社,監(jiān)聽其中的點擊屬性
- 我們拿到所有的圖片和標(biāo)題屬性
- 配置 TabBar 樣式管理類
- 監(jiān)聽TabBarItem 的點擊操作(這里由于 RxSwift 會變得特別簡單)
- 處理 TabBarItem 的點擊事件
fileprivate func configureTabBar() {
// 拿到五個 BarItem 上的元素(圖片和標(biāo)題)
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: "我的")
]
// 創(chuàng)建管理樣式類
let tabBarStyle = TabBarStyle(elements: barItemElements)
// 拿到樣式類抠刺,創(chuàng)建 TabBarView
let tabBarView = TabBarView(themeStyle: tabBarStyle)
self.view.addSubview(tabBarView)
// 監(jiān)聽 TabBarView 的 selectedIndexes 屬性,當(dāng)有點擊操作時摘昌,觸發(fā)處理點擊事件的方法
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)
}
// 添加當(dāng)前選擇的控制器速妖,并顯示
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,到這里聪黎,我們一個簡單的項目框架就已經(jīng)完成买优,其效果如下:
你可以在 https://github.com/iJudson/RxBasicInterface <TabBarView 在 MenuViewController 的使用> 的分支上拿到迄今為止的代碼。
后續(xù)
當(dāng)然一個簡單的項目框架還不止這些可能會有:
- 導(dǎo)航欄 - NavigationBar
- 頂部選擇欄 - TopBarColumnView
而這兩個 View 的創(chuàng)建跟 TabBarView 的創(chuàng)建是一模一樣的挺举,大家都試著去創(chuàng)建體會下杀赢。我這里也不再贅余,而最終的效果如下:
最終的代碼湘纵,大家可以在 https://github.com/iJudson/RxBasicInterface 中找到脂崔。
如果這個基本框架還有哪些地方可以繼續(xù)優(yōu)化,請不吝評論告知我梧喷。
如果你覺得這個基本框架還不錯砌左,歡迎 star脖咐。
感謝!