与国外不同。StoryBoard自推出以来就受到了国内开发者的质疑。质疑的理由有很多。什么不利于多人协作?问题不易测试,降低执行效率等。本文就是对这些问题的举例和分析。StoryBoard和Xib有什么区别?StoryBoard和Xib都是视图序列化工具,用于分离UI样式代码,提高视图代码复用,增加所见即所得,降低视图测试的复杂度。其中,Xib主要是基于View的。StoryBoard和Xib有什么区别。StoryBoard主要是基于控制器Controller和它们之间的关系,以及与视图View的关系。StoryBoard和Xib不利于多人协作。git合并代码容易冲突难处理?这是污蔑StoryBoard最多的理由,似乎也是最充分的理由。这种失败最显着的例子如下所示。Storyboard的不利画面在Storyboard中,大量的Controller控制器和Segue连接突出了错综复杂的UI关系,令人望而生畏或难以维护。但这不应该是Storyboard的错,只是用户滥用工具而已!是的,就是滥用,不管是Storyboard还是纯代码,本质上都是工具,工具本身没有正邪之分。该工具是用户。即使是纯代码开发,如果没有命名约定,肆意嵌套if,不遵循MVC或MVVM等开发模式,不区分开发环境和生产环境,代码怎么写这样谈可维护性,很多人会不兼容。合作呢?那么,反过来说,Storyboard怎么用才算不被滥用呢?要避免滥用,最好的办法就是自定义规范,就像代码中的许多规范一样。每个团队可能都有自己的喜好,所以我在这里介绍一些想法,并列出我们团队使用Storyboard的规范,供大家参考。每个模块都有一个独立的Storyboard。每个Storyboard在同一页上只能有一个主VC和一个子VC。主VC不应超过两个。在一个项目中,Storyboard不应该孤立存在。它应该像MVP模式。每个页面都有一个独立的Storyboard。每个Storyboard在同一页上只能有一个主VC和一个子VC。不应该有两个主要的VC。多于。(在大多数情况下,Storyboard上应该只有一个VC)。页面之间的segue连接应该使用StroyboardReferenceScene,UITabBarController的子页面因为比较复杂应该作为主VC处理。view的初始样式尽量在Storyboard的属性面板中设置。除非是极其特殊的情况,布局也应该在Storyboard上使用各种约束来完成。这样有利于视图样式和视图代码的分离,有利于视图代码复用性和兼容性的提高。对于逻辑复杂的VC,需要添加Object对象,并绑定相应的类到单独的逻辑代码中。对于圆角、背景色、阴影等CALayer样式,应采用扩展或子类实例的形式,使用@IBInspectable属性关键字,在Storyboard属性面板中设置初始样式。对于自定义视图,您应该使用@IBDesignable关键字以确保您在Storyboard上所见即所得!利用以上原则,只要任务分工合理,基本不会出现多人同时修改同一个Storyboard的情况,即使不小心出现了协调错误,精简后的Storyboard代码量小,并且借助文件比较工具很容易处理git冲突。毕竟,一个臃肿的Storyboard和一个臃肿的ViewController一样,很难维护并且容易产生git冲突。唯一的解决办法是谨慎使用工具。StoryBoard和Xib隐藏UI细节容易导致ViewController臃肿?与其说StoryBoard和Xib隐藏了UI细节,苹果更希望通过它们来引导开发者正确使用视图和控制器。当他们创建视图实例时,所有的视图实例都是通过所需的init?(coderaDecoder:NSCoder){}构造方法创建的。所有初始样式都是在属性面板中设置的值,通过funcsetValue(_value:Any?,forUndefinedKeykey:String){......}赋值给view的相应属性。至于那个臃肿的ViewController,就更可笑了。StoryBoard提供了多种分离代码的解决方案,但是很多人并不知道。以美团首页UI为例:这样的首页比较复杂,一个普通的布局需要多个CollectionView和一个UITableView;如果这些视图的委托都由ViewController实现,自然会显得臃肿和混乱。一般手写学校会分离出3个ChildViewController来解决臃肿的问题。难道Storyboard做不到?答案是不。在很早的版本中,苹果给出了上图中的解决方案。占位符容器视图指向子控制器的EmbedSegue;按住Control键连接到你要包含的子控制器,占位符视图的实例==子控制器的视图(子控制器的根视图);选择Embed连接方式后,子控制器的尺寸变为与placeholderview一样大;这样我们就可以把功能图标的CollectionView的代码放在第一个子控制器上,CollectionViewDelegate、CollectionViewDataSource等代码也由子控制器Controller实现;同样,可以在折扣区添加一个ContainerView,指向第二个子控制器。通过ContainerView创建的ChildViewController如何与主ViewController传递参数或者相互调用?ChildViewController可以通过self.parent(Swift)||获取主ViewController实例self.parentViewController(OC)。主ViewController可以通过self.chilren(Swift)||获取ChildViewController实例self.childViewControllers(OC),它是一个数组,其顺序等于占位符视图在视图层次结构中的顺序。值得一提的是,这种方式创建的ChildViewController的构造方法晚于主ViewController,但是生命周期中的viewDidLoad早于主ViewController,所以在ChildViewController中的viewDidLoad方法中,self.parent为nil,也就是说无法获取主ViewController实例。如果在初始化时需要获取主ViewController的实例,则应在主ViewController``viewDidLoad方法中调用ChildViewController的具体方法,并将self作为参数传递。此外,还可以使用Object对象。将其添加到控制器上方。它的本质是继承自NSObject的一个子类,我们完全可以把它看成是一个小功能模块的控制器。类FeaturesController:NSObject,UICollectionViewDataSource,UICollectionViewDelegate{@IBOutletweakvarcollectionView:UICollectionView!funccollectionView(_collectionView:UICollectionView,numberOfItemsInSectionsection:Int)->Int{<#code#>}funccollectionView(_collectionViewtcindForlCollection,mA:IndexPath)->UICollectionViewCell{<#code#>}}选择对象Storyboard,绑定上面的类:在Object上右击,在弹出的菜单中连接:在CollectionView上右击设置Delegate和DataSource的连接:如果需要调用本模块的方法或者通过主ViewController中的参数:classHomeController:UIViewController{@IBOutletweakvarfeaturesController:FeaturesController!overridefuncviewDidLoad(){super.viewDidLoad()featuresController.datas=[...]featuresController.collectionView.reloadData()}}完成连接。同样,如果一个页面需要多个子模块,可以在Storyboard上拖拽多个Object,绑定不同的模块控件类。与占用ContainerView和ChildViewController方法相比,Object方法无论是传递参数还是相互调用都比较方便。缺点是没有ChildViewController的生命周期方法。如果要使用viewWillAppear等,需要在主ViewController的viewWillAppear中调用Object的自定义方法。通过以上两种方式不难看出,导致ViewController代码臃肿的不是Storyboard,而是设计不当。即使不使用Storyboard,将所有功能都写在一个ViewController中也一样臃肿。这都是用户决定的,不属于Storyboard的责任!是不是StoryBoard和Xib有问题,不好测试?这个问题其实很模糊。也咨询了很多人才知道他们所谓的问题不好测试,就是指以下两种情况:在修改或删除@IBOutlet的变量名时,没有处理对应的Storyboard,导致runtimecrash,而且崩溃内容无法理解!当绑定的类名改变时,没有处理对应的Storyboard,导致运行时Crash,无法理解crash的内容!其实只要知道Apple是如何将Storyboard的xml解析成view的,crash的报错内容就很好理解了。如前所述,视图构造使用以下方法:requiredinit?(coderaDecoder:NSCoder){}如果绑定的类名发生变化,则输出错误:InterfaceBuilder文件中的Unknownclass_TtC11ProjectName14HomeController。//InterfaceBuilder文件中的SwiftUnknown类HomeController。//ObjectiveC提示InterfaceBuilderfilethroughtheaboveerror指的是通过Storyboard或Xib构建视图或控制器,但是找不到名为HomeController的控制器。到这里你应该明白了,我们的其中一个Storyboard绑定了一个名为HomeController的controller,但是在代码中是找不到的。它可能已被重命名或删除。这时候可以全局搜索:在搜索结果中可以看到HomeController绑定了Main.storyboard,在Test.swift文件中定义了这个类,但是因为改名了找不到。不使用Storyboard能避免这样的问题吗?答案是否定的,因为在重构代码的时候,改变一个地方而忽略的例子很多。即使是纯代码也是一样的。因此,如果需要修改类名或变量名,应该善用Xcode的重构功能,而不是简单地直接修改。如果这样修改类名或变量名,Storyboard或Xib上绑定或连接的内容也会同步改变。不会有错误。同样,@IBOutlet连接的属性通过以下方法给视图赋值。funcsetValue(_value:Any?,forUndefinedKeykey:String){......}如果更改变量名,会出现如下错误:***Terminatingappduetouncaughtexception'NSUnknownKeyException',原因:'[setValue:forUndefinedKey:]:此类不符合关键featuresController的键值编码。当该方法找不到对应的属性时,会抛出异常。这里的意思是找不到featuresController这个属性,通过全局搜索可以发现在代码中改了名字。解决办法也是在修改变量名的时候删除对应的连接或者使用重构。由此可见,所谓测试难,完全是重构不慎,不了解构建过程,否则很容易定位问题并修改。而如果在重构代码的时候使用Xcode的重构功能,连问题都不会出现。StoryBoard和Xib会降低执行效率吗?这个问题好像是这样的。StoryBoard和Xib本质上是XML。要将它们解析为视图,需要对它们进行反序列化。肯定不如直接创建代码快,但这只是一种感觉。影响有多大?我们来测试一下:varcontrollers:[ViewController]=[]letcount=30000controllers.reserveCapacity(count)guardletsb=storyboardelse{return}varbeginTime=CACurrentMediaTime()for_in0..
