仿斗鱼界面项目架构分析
写在前面:赶在2016年结束前总结总结最近学习的知识。
申明:此项目为参考GitHub上是模仿斗鱼部分界面的一个项目(并不是斗鱼真正的源码)。我记录了一下个人对此项目的一些看法,其实我只是大自然的搬运工。
一,界面预览
作为一个像我这样的小白来说看到这样的界面顿时感觉如果是我来做的话是无从下手的,但无论多么复杂的项目都是通过一个模块一个模块拼接而成的。下面让我由简到繁来剖析项目实现过程。
首先,我们可以看到应用下方有四个选项卡,通过点击某个选项卡来切换不同页面。这个比较简单,我就不做详细介绍。
接下来,我们看首页部分
分析:上图中的模块一和模块四都是固定的比较好办,而模块二和模块三是连动的,点击了模块二中的某个选项卡,模块三也需要切换视图。
二,构建选项卡
1,根据由简到繁的原则,我们继续细分,先把模块三抛开,将模块二四个选项卡做出来。
2,类似这种选项卡切换的效果当前移动端应用是可谓是比比皆是,说不定什么时候我们就有这样的开发需求。
3,特别是项目规模达到一定程度后,类似这种选项卡很有可能在项目中会得到重复利用,因此我们又可以将选项卡的项目部分抽象出来,以方便后续使用。关于选项卡的个数,显示文本,位置都可以交给初始化器,创建本类实例时指定。同时设置相关代理,至于代理做什么我们目前还不知道,只是留下一个协议(类似Java的接口),让其实现类去完成代理的一些方法。如此一来这个类既可以提高复用性,同时具备可拓展性。
三,构建滚动视图
1,创建滚动视图原理与选项卡类似,不同的是在这里先创建一个UICollectionView,根据你所需要加入几个滑动页面将其作为子视图添加到UICollectionView中。
2,要从第一页滑到第二页其实类似于做了一个scrollView,设置相关属性让它每次滑动都是滑动整页,并不会出现滑动了一点两个页面都显示了一部分。
四,设置选项卡与滚动视图联动
效果如下:
解析:先从点击上方的选项卡也就是我们的label开始需要改变下面的视图
1,给label添加点击手势并调用其触发方法
1 // 5.给Lable添加手势 2 label.isUserInteractionEnabled = true 3 //Lable点击后先触发本身的监听方法 4 let tapGes = UITapGestureRecognizer(target: self, action: #selector(titleLabelClick(_:))) 5 label.addGestureRecognizer(tapGes)
2,点击label后先执行本身一些业务,(例如:改变label字体颜色等),再通知代理执行代理方法
1 @objc fileprivate func titleLabelClick(_ tapGes: UITapGestureRecognizer) { 2 //... 3 4 // 7.通知代理,代理具体业务交给其实现类 5 delegate?.pageTitltView(self, selectedIndex: currentIndex) 6 }
3,通过最后一层代理才是真正进行页面切换的业务逻辑
1 // MARK:- 遵守PageTitleViewDelegate协议 2 extension HomeViewController : PageTitleViewDelegate { 3 func pageTitltView(_ titleView: PageTitleView, selectedIndex index: Int) { 4 pageContentView.setCurrentIndex(index) 5 } 6 }
小结:通过上面的效果图中我们不难发现,通过点击选项卡可以切换视图,拖动下发视图同样可以切换视图并改变选项卡是否选中的状态。这是如何实现的呢?其实和上面的原理是一致的,都是通过先触发本身控件的监听方法再通知代理调用对方的某些方法。做到这一步,这个项目的大概雏形也出来了。
五,系统控件拓展
1 /* 2 对系统的一些控件进行拓展方便项目中使用 3 */ 4 5 extension UIColor { 6 7 // MARK:- 遍历构造器 8 convenience init(r: CGFloat, g: CGFloat, b: CGFloat) { 9 self.init(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: 1.0) 10 } 11 12 // MARK:- 随机色 13 class func randomColor() -> UIColor { 14 return UIColor(r: CGFloat(arc4random_uniform(256)), g: CGFloat(arc4random_uniform(256)), b: CGFloat(arc4random_uniform(256))) 15 } 16 }
解析:当我们在看别人项目源码的时候发现他调用了一个系统本没有的方法,这个时候第一反应应该想到是否是导入了第三方库或是自己对系统某个对象进行了拓展。
优点:通过这种方式我们可以避免调用系统又臭又长的方法和其他一些便利的方法。
缺点:同时这种方式不能滥用,不然一个类中大都是程序员自己定义的方法,会降低代码的可读性。一个项目往往是多个人合作完成的,你能理解的别人未必能。
六,第三方库的封装
1 // MARK:- 方法枚举类型 2 enum MethodType { 3 case get 4 case post 5 } 6 7 // MARK:- 网络请求工具类 8 class NetworkTools { 9 class func requestData(_ type : MethodType, URLString : String, parameters : [String : Any]? = nil, finishedCallback: @escaping (_ result : Any) -> ()) { 10 11 // 1.获取类型 12 let method = type == .get ? HTTPMethod.get : HTTPMethod.post 13 14 // 2.发送网络请求 15 Alamofire.request(URLString, method: method, parameters: parameters).responseJSON { (response) in 16 // 3.获取结果 17 guard let result = response.result.value else { 18 print(response.result.error) 19 return 20 } 21 // 4.将结果回调出去 22 finishedCallback(result) 23 } 24 } 25 }
解析:框架与代码的分离
1,无论是哪种语言在开发中总是避免不了需要引入第三方框架,例如本例中需要引入第三方库之一Alamofire。我们在这里对其进行封装是为了使我们的代码与框架进行解耦。
2,在一个项目中或多或少需要引用各种各样的框架使我们的项目更加强健,稳固。同时,我们也不能过分依赖于框架,使我们的项目过分的与框架耦合在一起。
3,假如,我们发现在我们项目中某套框架存在很大的漏洞,又或者是有一套全新的更加完善的框架出现,需要替换框架时,此时我们才发现我们的代码与框架已经紧密耦合在一起了。这个时候如需要替换的话,对我们的成本来说无疑是巨大的打击。
4,封装后在项目其他文件中便不在需要引入Alamofire库,要使用它的方法只需调用我们自己封装的方法,需要替换框架时再重新实现相关方法即可。
七,总结:
1,对于我这种小菜鸟如果要我来开发这个项目恨不得用一个类写完所有的方法然后不停的调用即可。但是....大家都知道。如果一个项目的架构以十分来评论的话我想我的思维深度恐怕只有两分。
2,之前小打小闹也开发过几个练手的小项目一股脑的写下去,在一个地方卡住了,整个项目也就废了,此时对于一个项目的整体架构与思维模型显得尤为重要。
3,不得不吐槽下swift语言版本差异太大很多方法对象改变很大,同时在使用Xcode开发过程中有些控件的属性是在工具面板中配置的而有些是在代码中配置的,在项目调试过程中很难发现问题。索性我更愿意在开发中控件完全由代码生成,总之,综合上述因素,给我们开发带来了不少困扰,不知道你们有何感想。
4,差点忘记提供链接:https://github.com/coderwhy/DouYuZB 大家可以下载源码感受下原作者的思维深度。
写给自己:革命尚未成功,2017更要努力!