通过服务器返回的数据来创建子控制器

  如果你有看过这个项目之前的代码,肯定知道我在搭建首页模块的时候,是通过离线数组来创建子控制器的:

/// 创建子控制器
private func setupChildViewControllers() {
    
    // FIXME: - 从网络获取标题的Tabs,然后通过JSON来设置标题
    // 创建子控制器的标题
    let titles = ["分类", "推荐", "精品", "直播", "广播"]
    
    
    // 创建标题样式
    let titleStyle = TitleStyle()
    titleStyle.titleViewHeight = 44
    titleStyle.isScrollEnable = false  // 设置标题下面的指示器是否可以滚动(其实默认为不可以滚动)
    titleStyle.selectedTextColor = UIColor(r: 246, g: 91, b: 90)  // 设置选中标题的颜色
    titleStyle.scrollSlideBackgroundColor = UIColor(r: 246, g: 91, b: 90)  // 设置滚动指示器的背景颜色
    titleStyle.isShowScrollSlide = true  // 需要滚动指示器
    titleStyle.isNeedScale = false  // 需要对选中标题进行缩放
    titleStyle.titleFont = UIFont.systemFont(ofSize: 15)  // 设置子控制器标题文字大小
    titleStyle.titleBackgroundColor = UIColor(r: 246, g: 246, b: 246)  // 设置子控制器标题的背景颜色
    
    
    // 创建一个数组,用来存放子控制器
    var childVcs = [UIViewController]()
    
    
    // 创建子控制器并将其添加到childVcs数组中
    childVcs.append(CategoryViewController())  // 分类子控制器
    childVcs.append(RecommendViewController())  // 推荐子控制器
    childVcs.append(BoutiqueViewController())  // 精品子控制器
    childVcs.append(LiveViewController())  // 直播子控制器
    childVcs.append(BroadcastViewController())  // 广播子控制器
    
    // 创建containerView的frame
    // - 注意:设置containerView的高度时,一定不要忘记减去
    // - 状态栏、导航栏和tabBar的高度,否则,后面在相应控制
    // - 器的view中添加内容时,会导致有一部分内容被tabBar给
    // - 遮挡的情况出现
    let containerFrame = CGRect(x: 0, y: kStatusBarHeight + kNavigationBarHeight, width: kScreenWidth, height: kScreenHeight - kStatusBarHeight - kNavigationBarHeight - kTabBarHeight - kTabBarMargin)
    
    // 调用自定义构造函数,根据实际需求创建合适的ContainerView对象
    let containerView = ContainerView(frame: containerFrame, titles: titles, titleStyle: titleStyle, childVcs: childVcs, parentVc: self)
    
    // 将创建好的ContainerView对象添加到当前控制器的View中
    view.addSubview(containerView)
}

  也就是说,我们事先在本地确定好子控制器的标题和数量,然后再创建子控制器。这是最常规的做法,而且也可能是性能最好的做法。但是,如果是相对于一个重度依赖网络数据,并且有可能需要对标题、子控制器数量,以及子控制器选中状态进行动态修改的应用来说,这种做法其实并不灵活。好的做法是,通过服务器返回的数据来确定标题及其数量,这样我们就可以灵活的修改数据,而不用重新上架应用了。

通过网络数据来确定子控制器标题.png

  接下来,我们所要做的就是,发送网络数据,然后对服务器返回的数据进行解析,最后再将解析完成的标题存放到数组中,之后再通过这个数组来创建子控制器及其标题。首先我们来看一下如何发送网络数据:

/// RequestURL
private let kRequestURL = "http://recpage.c.qingting.fm/v3/navbar"

class NavBarViewModel: NSObject {
    
    /// 用于存储转换完成的模型数据
    lazy var navBarModelArray = [NavBarModel]()
}


extension NavBarViewModel {

    /// 请求网络数据并将其转换为模型
    func requestData(completionHandler: @escaping () -> ()) {
        
        // 通过Alamofrie来发送网络请求
        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数据转成字典
            guard let resultDict = result as? [String: Any] else { return }
            
            /// 根据字典中的关键字data取出字典中的数组数据
            guard let resultArray = resultDict["data"] as? [[String: Any]] else { return }
            
            /// 遍历数组resultArray,取出它里面的字典
            for dict in resultArray {
                
                // 将字典转为模型
                let item = NavBarModel(dict: dict)
                
                // 将转换完成的模型存储起来
                self.navBarModelArray.append(item)
            }
            
            // 数据回调
            completionHandler()
        }
    }
}

  在将网络数据转成模型的过程中,我们没有借助任何的第三方框架,是直接通过KVC来完成的。在设计模型文件的时候,需要对服务器返回的JSON数据进行分析:

JSON数据分析.png

  上面返回的这个JSON数据比较简单,基本上没有什么嵌套,并且唯一的一个嵌套字典link没什么用,我们可以不用解析。另外,需要特别强调的是,在Swift 4中利用KVC进行字典转模型的时候,一定不要忘记在类的定义前面加上属性关键字@objcMembers,否则键值匹配会失效:

@objcMembers
class NavBarModel: NSObject {
    
    // MARK: - 服务器返回的模型属性
    
    /// 标题
    var title: String = ""
    
    /// urlScheme
    var urlScheme: String = ""
    
    /// 当前子控制器是否被选中
    var current: Bool = false
    
    // MARK: - 自定义构造函数
    
    /// 将字典转为模型
    init(dict: [String: Any]) {
        super.init()
        
        // 利用KVC将字典转为模型
        setValuesForKeys(dict)
    }
    
    override func setValue(_ value: Any?, forUndefinedKey key: String) { }
}

  网络数据请求和字典转模型的工作都做完了之后,再回到控制器中,修改创建子控制器的代码。当然,前面一定要声明一个viewModel属性,用来请求数据。有了数据之后,就可以从模型性取出标题了,然后就可以通过网络数据来创建子控制器及其标题了:

/// 创建子控制器
private func setupChildViewControllers() {
    
    // 发送网络请求,获取网络上的标题
    navBarViewModel.requestData {
        
        // 从模型中取出标题,并且将其存放到一个数组中
        let titles = self.navBarViewModel.navBarModelArray.map({ $0.title })
        
        // 创建标题样式
        let titleStyle = TitleStyle()
        titleStyle.titleViewHeight = 44
        titleStyle.isScrollEnable = false  // 设置标题下面的指示器是否可以滚动(其实默认为不可以滚动)
        titleStyle.selectedTextColor = UIColor(r: 246, g: 91, b: 90)  // 设置选中标题的颜色
        titleStyle.scrollSlideBackgroundColor = UIColor(r: 246, g: 91, b: 90)  // 设置滚动指示器的背景颜色
        titleStyle.isShowScrollSlide = true  // 需要滚动指示器
        titleStyle.isNeedScale = false  // 需要对选中标题进行缩放
        titleStyle.titleFont = UIFont.systemFont(ofSize: 15)  // 设置子控制器标题文字大小
        titleStyle.titleBackgroundColor = UIColor(r: 246, g: 246, b: 246)  // 设置子控制器标题的背景颜色
        
        
        // 创建一个数组,用来存放子控制器
        var childVcs = [UIViewController]()
        
        
        // 创建子控制器并将其添加到childVcs数组中
        childVcs.append(CategoryViewController())  // 分类子控制器
        childVcs.append(RecommendViewController())  // 推荐子控制器
        childVcs.append(BoutiqueViewController())  // 精品子控制器
        childVcs.append(LiveViewController())  // 直播子控制器
        childVcs.append(BroadcastViewController())  // 广播子控制器
        
        
        // 创建containerView的frame
        // - 注意:设置containerView的高度时,一定不要忘记减去
        // - 状态栏、导航栏和tabBar的高度,否则,后面在相应控制
        // - 器的view中添加内容时,会导致有一部分内容被tabBar给
        // - 遮挡的情况出现
        let containerFrame = CGRect(x: 0, y: kStatusBarHeight + kNavigationBarHeight, width: kScreenWidth, height: kScreenHeight - kStatusBarHeight - kNavigationBarHeight - kTabBarHeight - kTabBarMargin)
        
        // 调用自定义构造函数,根据实际需求创建合适的ContainerView对象
        let containerView = ContainerView(frame: containerFrame, titles: titles, titleStyle: titleStyle, childVcs: childVcs, parentVc: self)
        
        // 将创建好的ContainerView对象添加到当前控制器的View中
        self.view.addSubview(containerView)
    }
}

  原本只是一行代码的事情,而我们却多搞了两个文件,一个NavBarViewModel文件,以及一个NavBarModel文件,并且还多写了好多代码,这么做绝对不是为了装逼,而是有着非常明确的现实需求——不必通过重新提交应用到App Store就可以动态的修改子控制器的标题及其数量。当然,这个也不是随便就能修改的,前提是项目中有与之对应的类。项目代码参见QTRadio

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容