??如果你有看過這個(gè)項(xiàng)目之前的代碼,肯定知道我在搭建首頁模塊的時(shí)候,是通過離線數(shù)組來創(chuàng)建子控制器的:
/// 創(chuàng)建子控制器
private func setupChildViewControllers() {
// FIXME: - 從網(wǎng)絡(luò)獲取標(biāo)題的Tabs,然后通過JSON來設(shè)置標(biāo)題
// 創(chuàng)建子控制器的標(biāo)題
let titles = ["分類", "推薦", "精品", "直播", "廣播"]
// 創(chuàng)建標(biāo)題樣式
let titleStyle = TitleStyle()
titleStyle.titleViewHeight = 44
titleStyle.isScrollEnable = false // 設(shè)置標(biāo)題下面的指示器是否可以滾動(dòng)(其實(shí)默認(rèn)為不可以滾動(dòng))
titleStyle.selectedTextColor = UIColor(r: 246, g: 91, b: 90) // 設(shè)置選中標(biāo)題的顏色
titleStyle.scrollSlideBackgroundColor = UIColor(r: 246, g: 91, b: 90) // 設(shè)置滾動(dòng)指示器的背景顏色
titleStyle.isShowScrollSlide = true // 需要滾動(dòng)指示器
titleStyle.isNeedScale = false // 需要對(duì)選中標(biāo)題進(jìn)行縮放
titleStyle.titleFont = UIFont.systemFont(ofSize: 15) // 設(shè)置子控制器標(biāo)題文字大小
titleStyle.titleBackgroundColor = UIColor(r: 246, g: 246, b: 246) // 設(shè)置子控制器標(biāo)題的背景顏色
// 創(chuàng)建一個(gè)數(shù)組,用來存放子控制器
var childVcs = [UIViewController]()
// 創(chuàng)建子控制器并將其添加到childVcs數(shù)組中
childVcs.append(CategoryViewController()) // 分類子控制器
childVcs.append(RecommendViewController()) // 推薦子控制器
childVcs.append(BoutiqueViewController()) // 精品子控制器
childVcs.append(LiveViewController()) // 直播子控制器
childVcs.append(BroadcastViewController()) // 廣播子控制器
// 創(chuàng)建containerView的frame
// - 注意:設(shè)置containerView的高度時(shí),一定不要忘記減去
// - 狀態(tài)欄、導(dǎo)航欄和tabBar的高度,否則,后面在相應(yīng)控制
// - 器的view中添加內(nèi)容時(shí),會(huì)導(dǎo)致有一部分內(nèi)容被tabBar給
// - 遮擋的情況出現(xiàn)
let containerFrame = CGRect(x: 0, y: kStatusBarHeight + kNavigationBarHeight, width: kScreenWidth, height: kScreenHeight - kStatusBarHeight - kNavigationBarHeight - kTabBarHeight - kTabBarMargin)
// 調(diào)用自定義構(gòu)造函數(shù),根據(jù)實(shí)際需求創(chuàng)建合適的ContainerView對(duì)象
let containerView = ContainerView(frame: containerFrame, titles: titles, titleStyle: titleStyle, childVcs: childVcs, parentVc: self)
// 將創(chuàng)建好的ContainerView對(duì)象添加到當(dāng)前控制器的View中
view.addSubview(containerView)
}
??也就是說,我們事先在本地確定好子控制器的標(biāo)題和數(shù)量,然后再創(chuàng)建子控制器。這是最常規(guī)的做法,而且也可能是性能最好的做法。但是,如果是相對(duì)于一個(gè)重度依賴網(wǎng)絡(luò)數(shù)據(jù),并且有可能需要對(duì)標(biāo)題、子控制器數(shù)量,以及子控制器選中狀態(tài)進(jìn)行動(dòng)態(tài)修改的應(yīng)用來說,這種做法其實(shí)并不靈活。好的做法是,通過服務(wù)器返回的數(shù)據(jù)來確定標(biāo)題及其數(shù)量,這樣我們就可以靈活的修改數(shù)據(jù),而不用重新上架應(yīng)用了。
??接下來,我們所要做的就是,發(fā)送網(wǎng)絡(luò)數(shù)據(jù),然后對(duì)服務(wù)器返回的數(shù)據(jù)進(jìn)行解析,最后再將解析完成的標(biāo)題存放到數(shù)組中,之后再通過這個(gè)數(shù)組來創(chuàng)建子控制器及其標(biāo)題。首先我們來看一下如何發(fā)送網(wǎng)絡(luò)數(shù)據(jù):
/// RequestURL
private let kRequestURL = "http://recpage.c.qingting.fm/v3/navbar"
class NavBarViewModel: NSObject {
/// 用于存儲(chǔ)轉(zhuǎn)換完成的模型數(shù)據(jù)
lazy var navBarModelArray = [NavBarModel]()
}
extension NavBarViewModel {
/// 請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)并將其轉(zhuǎn)換為模型
func requestData(completionHandler: @escaping () -> ()) {
// 通過Alamofrie來發(fā)送網(wǎng)絡(luò)請(qǐng)求
NetworkTools.shareTools.requestData(kRequestURL, .get, parameters: ["wt": "json", "v": "6.0.4", "deviceid": "093e8b7e24c02246fe92373727e4a92c", "phonetype": "iOS", "osv": "11.1.1", "device": "iPhone", "pkg": "com.Qting.QTTour"]) { (result) in
/// 將JSON數(shù)據(jù)轉(zhuǎn)成字典
guard let resultDict = result as? [String: Any] else { return }
/// 根據(jù)字典中的關(guān)鍵字data取出字典中的數(shù)組數(shù)據(jù)
guard let resultArray = resultDict["data"] as? [[String: Any]] else { return }
/// 遍歷數(shù)組resultArray,取出它里面的字典
for dict in resultArray {
// 將字典轉(zhuǎn)為模型
let item = NavBarModel(dict: dict)
// 將轉(zhuǎn)換完成的模型存儲(chǔ)起來
self.navBarModelArray.append(item)
}
// 數(shù)據(jù)回調(diào)
completionHandler()
}
}
}
??在將網(wǎng)絡(luò)數(shù)據(jù)轉(zhuǎn)成模型的過程中,我們沒有借助任何的第三方框架,是直接通過KVC來完成的。在設(shè)計(jì)模型文件的時(shí)候,需要對(duì)服務(wù)器返回的JSON數(shù)據(jù)進(jìn)行分析:
??上面返回的這個(gè)JSON數(shù)據(jù)比較簡單,基本上沒有什么嵌套,并且唯一的一個(gè)嵌套字典link沒什么用,我們可以不用解析。另外,需要特別強(qiáng)調(diào)的是,在Swift 4中利用KVC進(jìn)行字典轉(zhuǎn)模型的時(shí)候,一定不要忘記在類的定義前面加上屬性關(guān)鍵字@objcMembers,否則鍵值匹配會(huì)失效:
@objcMembers
class NavBarModel: NSObject {
// MARK: - 服務(wù)器返回的模型屬性
/// 標(biāo)題
var title: String = ""
/// urlScheme
var urlScheme: String = ""
/// 當(dāng)前子控制器是否被選中
var current: Bool = false
// MARK: - 自定義構(gòu)造函數(shù)
/// 將字典轉(zhuǎn)為模型
init(dict: [String: Any]) {
super.init()
// 利用KVC將字典轉(zhuǎn)為模型
setValuesForKeys(dict)
}
override func setValue(_ value: Any?, forUndefinedKey key: String) { }
}
??網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求和字典轉(zhuǎn)模型的工作都做完了之后,再回到控制器中,修改創(chuàng)建子控制器的代碼。當(dāng)然,前面一定要聲明一個(gè)viewModel屬性,用來請(qǐng)求數(shù)據(jù)。有了數(shù)據(jù)之后,就可以從模型性取出標(biāo)題了,然后就可以通過網(wǎng)絡(luò)數(shù)據(jù)來創(chuàng)建子控制器及其標(biāo)題了:
/// 創(chuàng)建子控制器
private func setupChildViewControllers() {
// 發(fā)送網(wǎng)絡(luò)請(qǐng)求,獲取網(wǎng)絡(luò)上的標(biāo)題
navBarViewModel.requestData {
// 從模型中取出標(biāo)題,并且將其存放到一個(gè)數(shù)組中
let titles = self.navBarViewModel.navBarModelArray.map({ $0.title })
// 創(chuàng)建標(biāo)題樣式
let titleStyle = TitleStyle()
titleStyle.titleViewHeight = 44
titleStyle.isScrollEnable = false // 設(shè)置標(biāo)題下面的指示器是否可以滾動(dòng)(其實(shí)默認(rèn)為不可以滾動(dòng))
titleStyle.selectedTextColor = UIColor(r: 246, g: 91, b: 90) // 設(shè)置選中標(biāo)題的顏色
titleStyle.scrollSlideBackgroundColor = UIColor(r: 246, g: 91, b: 90) // 設(shè)置滾動(dòng)指示器的背景顏色
titleStyle.isShowScrollSlide = true // 需要滾動(dòng)指示器
titleStyle.isNeedScale = false // 需要對(duì)選中標(biāo)題進(jìn)行縮放
titleStyle.titleFont = UIFont.systemFont(ofSize: 15) // 設(shè)置子控制器標(biāo)題文字大小
titleStyle.titleBackgroundColor = UIColor(r: 246, g: 246, b: 246) // 設(shè)置子控制器標(biāo)題的背景顏色
// 創(chuàng)建一個(gè)數(shù)組,用來存放子控制器
var childVcs = [UIViewController]()
// 創(chuàng)建子控制器并將其添加到childVcs數(shù)組中
childVcs.append(CategoryViewController()) // 分類子控制器
childVcs.append(RecommendViewController()) // 推薦子控制器
childVcs.append(BoutiqueViewController()) // 精品子控制器
childVcs.append(LiveViewController()) // 直播子控制器
childVcs.append(BroadcastViewController()) // 廣播子控制器
// 創(chuàng)建containerView的frame
// - 注意:設(shè)置containerView的高度時(shí),一定不要忘記減去
// - 狀態(tài)欄、導(dǎo)航欄和tabBar的高度,否則,后面在相應(yīng)控制
// - 器的view中添加內(nèi)容時(shí),會(huì)導(dǎo)致有一部分內(nèi)容被tabBar給
// - 遮擋的情況出現(xiàn)
let containerFrame = CGRect(x: 0, y: kStatusBarHeight + kNavigationBarHeight, width: kScreenWidth, height: kScreenHeight - kStatusBarHeight - kNavigationBarHeight - kTabBarHeight - kTabBarMargin)
// 調(diào)用自定義構(gòu)造函數(shù),根據(jù)實(shí)際需求創(chuàng)建合適的ContainerView對(duì)象
let containerView = ContainerView(frame: containerFrame, titles: titles, titleStyle: titleStyle, childVcs: childVcs, parentVc: self)
// 將創(chuàng)建好的ContainerView對(duì)象添加到當(dāng)前控制器的View中
self.view.addSubview(containerView)
}
}
??原本只是一行代碼的事情,而我們卻多搞了兩個(gè)文件,一個(gè)NavBarViewModel文件,以及一個(gè)NavBarModel文件,并且還多寫了好多代碼,這么做絕對(duì)不是為了裝逼,而是有著非常明確的現(xiàn)實(shí)需求——不必通過重新提交應(yīng)用到App Store就可以動(dòng)態(tài)的修改子控制器的標(biāo)題及其數(shù)量。當(dāng)然,這個(gè)也不是隨便就能修改的,前提是項(xiàng)目中有與之對(duì)應(yīng)的類。項(xiàng)目代碼參見QTRadio。