??如果你有看過(guò)這個(gè)項(xiàng)目之前的代碼,肯定知道我在搭建首頁(yè)模塊的時(shí)候粉洼,是通過(guò)離線數(shù)組來(lái)創(chuàng)建子控制器的:
/// 創(chuàng)建子控制器
private func setupChildViewControllers() {
// FIXME: - 從網(wǎng)絡(luò)獲取標(biāo)題的Tabs节预,然后通過(guò)JSON來(lái)設(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ù)組,用來(lái)存放子控制器
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)
}
??也就是說(shuō)锅棕,我們事先在本地確定好子控制器的標(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)用來(lái)說(shuō)移稳,這種做法其實(shí)并不靈活蕴纳。好的做法是,通過(guò)服務(wù)器返回的數(shù)據(jù)來(lái)確定標(biāo)題及其數(shù)量个粱,這樣我們就可以靈活的修改數(shù)據(jù)古毛,而不用重新上架應(yīng)用了。
??接下來(lái)都许,我們所要做的就是稻薇,發(fā)送網(wǎng)絡(luò)數(shù)據(jù),然后對(duì)服務(wù)器返回的數(shù)據(jù)進(jìn)行解析梭稚,最后再將解析完成的標(biāo)題存放到數(shù)組中颖低,之后再通過(guò)這個(gè)數(shù)組來(lái)創(chuàng)建子控制器及其標(biāo)題。首先我們來(lái)看一下如何發(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 () -> ()) {
// 通過(guò)Alamofrie來(lái)發(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ǔ)起來(lái)
self.navBarModelArray.append(item)
}
// 數(shù)據(jù)回調(diào)
completionHandler()
}
}
}
??在將網(wǎng)絡(luò)數(shù)據(jù)轉(zhuǎn)成模型的過(guò)程中忱屑,我們沒(méi)有借助任何的第三方框架,是直接通過(guò)KVC來(lái)完成的暇昂。在設(shè)計(jì)模型文件的時(shí)候莺戒,需要對(duì)服務(wù)器返回的JSON數(shù)據(jù)進(jìn)行分析:
??上面返回的這個(gè)JSON數(shù)據(jù)比較簡(jiǎn)單,基本上沒(méi)有什么嵌套急波,并且唯一的一個(gè)嵌套字典link沒(méi)什么用从铲,我們可以不用解析。另外澄暮,需要特別強(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屬性窃蹋,用來(lái)請(qǐng)求數(shù)據(jù)。有了數(shù)據(jù)之后静稻,就可以從模型性取出標(biāo)題了警没,然后就可以通過(guò)網(wǎng)絡(luò)數(shù)據(jù)來(lái)創(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ù)組杀迹,用來(lái)存放子控制器
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í)需求——不必通過(guò)重新提交應(yīng)用到App Store就可以動(dòng)態(tài)的修改子控制器的標(biāo)題及其數(shù)量。當(dāng)然购裙,這個(gè)也不是隨便就能修改的懂版,前提是項(xiàng)目中有與之對(duì)應(yīng)的類。項(xiàng)目代碼參見(jiàn)QTRadio躏率。