iOS界面布局,代码还是IB?总有一款适合你

题图

Preface(废话)

iOS开发中一直有一个很具争议的话题,那就是界面布局到底是代码好还是使用IB(xib/storyboard)好?

有些人觉得手撸代码,才叫coder,使用代码的编译速度快(快不快苹果说了算)

有些人觉得IB的开发效率不容置疑,而且能减少界面代码对工程的污染

还有一些人觉得计算frame挺有意思…

好吧,如果还在使用initWithFrame()计算界面的布局,请在本文的留言区默默的扣个1...

本文不打算讨论哪个好,毕竟这么些年了,没有得出啥好的结论,代码的还是啪啪;IB也玩的很开心;

咱们把所有的方式都罗列一下,各位看官各取所需

手撸代码

Frame党

autolayout没有出来的时候,你使用frame也就不说啥了,因为在那个时候手机屏幕比较单一,就只有320*480一个,frame随便计算,但是现在这个时代那么多屏幕,你还是这个...

如果只是简单界面,你frame一下也没啥,请看下面的示例代码:

   // 分割线
    UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectMake(8, 58, Width - 16, 1)];
    label1.backgroundColor = SEPARATORLINE;
    [self addSubview:label1];
    
    UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectMake(8, 116, Width - 16, 1)];
    label2.backgroundColor = SEPARATORLINE;
    [self addSubview:label2];
    
    // 任务目标
    UILabel *aim = [[UILabel alloc] initWithFrame:CGRectMake(8, 8, 70, 21)];
    [self setLabel:aim WithTitle:@"任务目标:" withFont:FONT(15) withColor:PD_NAVI_COLOR];
    
    _taskAimLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 29, Width - 16, 21)];
    _taskAimLabel.font = FONT(13);
    [self addSubview:_taskAimLabel];
    
    // 任务奖励
    UILabel *award = [[UILabel alloc] initWithFrame:CGRectMake(8, 67, Width - 16, 21)];
    [self setLabel:award WithTitle:@"任务奖励:" withFont:FONT(15) withColor:PD_NAVI_COLOR];
    
    _taskAwardLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 88, Width - 16, 21)];
    _taskAwardLabel.font = FONT(13);
    [self addSubview:_taskAwardLabel];
    
    // 任务描述
    UILabel *des = [[UILabel alloc] initWithFrame:CGRectMake(8, 124, Width - 16, 21)];
    [self setLabel:des WithTitle:@"任务描述:" withFont:FONT(15) withColor:PD_NAVI_COLOR];
    
    _taskDescriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 145, Width - 16, 42)];
    _taskDescriptionLabel.font = FONT(13);
    _taskDescriptionLabel.numberOfLines = 2;
    [self addSubview:_taskDescriptionLabel];
    
    ... ...

我只想问,同学你真的不累么?你不累我这个维护者看着都累,如果是一个很长且布局不重复的界面,你咋办?

估计自己算着算着就晕了

所以,对于frame党,我只想说,你需要一个计算器...

手写Constraints

其实手写Constraints也是相当麻烦的,主要是因为Constraints有着非人的语法,给个例子大家随意感受一下

constraint = [  
    NSLayoutConstraint  
    constraintWithItem:testButton  
    attribute:NSLayoutAttributeCenterX  
    relatedBy:NSLayoutRelationEqual  
    toItem:self.view  
    attribute:NSLayoutAttributeCenterX  
    multiplier:1.0f  
    constant:00.0f  
];  

[self.view addConstraint:constraint];  

constraint = [  
    NSLayoutConstraint  
    constraintWithItem:testButton  
    attribute:NSLayoutAttributeBottom  
    relatedBy:NSLayoutRelationEqual  
    toItem:self.view  
    attribute:NSLayoutAttributeBottom  
    multiplier:1.0f  
    constant:-20.0f  
];  
  
[self.view addConstraint:constraint]; 

上面这段代码,只是添加了两个约束,试想一下,如果我们要写一个比较复杂的界面时,得写多长的代码?

不过,上面的代码可以优化

NSDictionary *viewsDic = NSDictionaryOfVariableBindings(deleteButton,cancelButton,nextButton);  
  
NSArray *constraints = nil;  
constraints = [NSLayoutConstraint constraintsWithVisualFormat:  
  @"H:|-25-[deleteButton(==cancelButton@700)]-(>=8)-[cancelButton(140)]-[nextButton(nextButtonWidth)]-rectY-|" 
               options:NSLayoutFormatAlignAllTop  
               metrics:@{@"rectY":@5,@"nextButtonWidth":@30}  
               views:viewsDic];  
[self.view addConstraints:constraints]; 

上面的代码看懂了么?

我自己都看不懂...

但是总有一些程序员和别的程序员不一样,他们能有不一样的能力,他们叫做大神,大神给我们封装了Masonry/Snapkit

Masonry/Snapkit

Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了,并具有高可读性,而且同时支持iOSMax OS X;SnapkitMasonryswift版本,语法也差不多

先看一个例子再说:

// masonry
[testButton mas_makeConstraints:^(MASConstraintMaker *make) {
    make.centerX.equalTo(self);
    make.size.mas_equalTo(CGSizeMake(50, 50));
    make.bottom.equalTo(hanupLabel.mas_top).offset(-10);
}];

// snapkit
testButton.snp_makeConstraints(closure: { make in
    make.centerX.equalTo(self);
    make.size.mas_equalTo(CGSizeMake(50, 50));
    make.bottom.equalTo(hanupLabel.mas_top).offset(-10);
})

控件直接调用mas_makeConstraints方法,在block中使用MASConstraintMaker进行相应的布局就行了,非常简洁

最重要的是可读性很高,在masonry里面有几点需要注意的地方:

  • 必须先把控件添加到父视图以后才能进行约束,不然会crash
  • mas_equalTo,在约束值为具体的数值(CGSize,CGPoint等也是)的时候需要使用这个
  • 内存管理,约束的block里面有时候会引起隐式内存泄露
  • 重复约束,或约束出现冲突的时候,在控制台会有log输出,发现以后改掉就好了
  • 如果多个约束在一行时,使用连接符语法(and),增强代码的可读性

上面几条是笔者在使用过程中总结的一些经验,大家可以参考一下,其实在这里内存管理方面,很值得说到;不过笔者在之前已经写过相关OC内存管理Swift内存管理的文章了大家可以出门左转进去看看,就能找到解决隐式内存泄露问题的办法😄

Neon

Neon其实也是一个swift版的autolayout框架,但是为什么把他单独拿出来说一下呢?Neon除了具有Snapkit的大部分优点以外,还有一个强大的功能,放一张官方图,你们随意感受一下

Neon

来个栗子吧:

view1.anchorToEdge(.Top, padding: padding, width: size, height: size)
view2.anchorToEdge(.Left, padding: padding, width: size, height: size)
view3.anchorToEdge(.Bottom, padding: padding, width: size, height: size)
view4.anchorToEdge(.Right, padding: padding, width: size, height: size)

上面这段代码的效果图如下:

Neon-Demo

有没有被震撼到,amazing😱有木有,而且Neon的作者经常直播写Neon库,感兴趣的可以去LivingCode看看,关于Neon更多的使用方法,大家可以去github上仔细研究,个人认为是非常不错的

XXAutoLayout

其实在github上还有很多autolayout的框架,xxxAutoLayout多了去了,但是我想说的是,适合自己的才是最好的,而且建议各位看官在选框架的时候,要多方考量,star数量,issue数量,支持度等等

不管如何,手写代码咱们先说到这里,下面看看Interface Builder

Interface Builder

关于IB的争议真的是太多了,文章长度都能绕地球xx圈;当然了,那都是别人的看法,那都是别人的观点,那都是别人写的文章;下面咱们具体看看到底好不好用,然后做结论

autolayout没有出来之前,就已经有IB了,但是那个时候IB不能算强大,只在Autolayout出来以后,IB才真的崭露头角,下面的很多例子我通过gif的方式给大家演示如何使用IB

基本用法

一个简单的居中

基本用法

上面这个例子演示了让一个100*100的view在屏幕中居中的方法,使用了垂直和水平居中,并且设置大小为100*100,约束的重点在于,给出的约束一定要能计算出控件的位置和大小,缺一不可;有时候给出的约束可能没有明确指出某个约束的值,但是可以通过给定条件计算出来,也是可以的,比如下面这个例子,做一个水平三等分

三等分

这里要记住一个快捷键command + option + ‘=‘(update constraint constant)

简单,快捷,而且没有代码,任何约束相关的代码都被隐藏在了IB的文件里面,只有你需要使用这个view的时候把他拖线出去就行了,如果需要修改控件的属性,直接在右侧的attributes inspector里面修改就可以了.

有些inspector里面没有给出的属性,也可以在identity inspector里面通过user defined runtime attributes修改

比如给一个view添加圆角,attributes inspector里面就没有给出相应的属性,如果使用代码修改你需要写一下两行代码:

view.layer.cornerRadius = 10.0
view.layer.masksToBounds = true

使用user defined runtime attributes,你只需要这样就可以了:

user defined runtime attributes

user defined runtime attributes里面可添加的属性有很多,大家可以大胆去探索

xib/storyboard里面不仅仅可以做autolayout和属性设置,还可以控件拖到代码里面使用,设置控件的代理,拖出控件自带的方法(在xib/storyboard里面叫action),以及给控件添加手势等等,看下面的demo:

delegate-outlets

这里有一点要说一下,我们做好的约束(constraint)也是可以拖出去的,而且可修改,很多使用xib/storyboard很久的同学都不知道,因为笔者之前见过这样的开发者:

他界面布局使用xib/storyboard,然后需要动态修改的时候他又使用Masonry修改(其实是又重新约束了一遍😢)

所以这里提醒大家,需要修改某个约束的时候直接拖出去,修改那条约束的constant值就OK

WOW✨✨✨✨

解放了有木有🎉🎉🎉

再也不用看那些臃肿的界面布局代码了

笔者第一次使用这些东西的时候,想起了那个夕阳下的奔跑,那是我逝去的青春(内牛满面😂)...

夕阳和青春啥关系?

Scrollview

xib上做scrollview约束的时候比较头疼,因为一不小心就会导致出来的界面不能滚动.这是因为,没有添加contentview的原因,如果没有contentview,scrollview就无法计算出自己的contentsize,所以无法滚动;

笔者在xib上使用scrollview的原则是,放完scrollview的时候就放contentview,然后在考虑子控件如何摆,各位同学仔细看下面的gif

Scrollview

这里具体的原理和解析细说起来都能另开一篇博客了,所以这里不做详细说明,有兴趣的同学可以搜一下,挺简单的

通过上面的demo,我们可以学到不只是scrollviewxib中的约束方法,还有如下几点:

  • 通过修改视图本身的长度为freedom,来约束长view
  • 快速添加约束,在demo的结尾笔者添加了2个button,并木有使用约束界面来添加,通过快捷键command + shift + option + ‘=‘来快速添加的

SizeClass

sizeclass是对不同尺寸的屏幕的区分,sizeclass把不同尺寸(包括横屏和竖屏)的屏幕进行了分类,无论是iPhone还是iPad设备,其宽度和高度都被划分为三种类型:Compact(紧凑)、Regular(正常)、Any(任意)我们只要针对于某一类型的屏幕进行布局,那么布局出来的界面可以显示在属于该类型的所有尺寸的屏幕上。

在开发中我们经常会遇到横屏处理问题,比如我们在横屏的时候会改变布局方式,比如隐藏和添加控件,下面这个例子演示一下在xib上使用sizeclass在横屏情况下添加一个button,并且在横屏情况下修改控件的属性,比如颜色:

SizeClass.gif

Xcode8以前,xib/storyboard上的sizeclass设置界面有些不同,但是使用方法还是一样的;

就上面这样简单的case,如果你用代码写,目测至少50+代码,这一点IB还是有优势的

Storyboard快速搭建静态页面

所谓静态页面就是那些不需要加载数据,或就算加载数据页面也还是长的一样,不会发生变化,比如微信的发现界面

发现

大家随便一看就知道,tableview

嗯,没错,但是如何写这个界面才好呢 ?这个tableview有4个section,有5个cell,好了分析完毕,开始撸代码吧,我们截取其中一部分代码看看(cellForRow里面的)

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CellID", for: indexPath)
    switch indexPath.section {
      case 0:
        cell.imageView?.image = UIImage(named: "")
        cell.textLabel?.text = ""
      case 1:
        cell.imageView?.image = UIImage(named: "")
        cell.textLabel?.text = ""
      case 2:
        cell.imageView?.image = UIImage(named: "")
        cell.textLabel?.text = ""
      case 3:
        switch indexPath.row {
          case 0:
            cell.imageView?.image = UIImage(named: "")
            cell.textLabel?.text = ""
          case 1:
            cell.imageView?.image = UIImage(named: "")
            cell.textLabel?.text = ""
        }
    }
}

就仅仅一个cellForRow就已经23行了,更别说didSelect方法里面的逻辑了,不用多说,再看看使用storyboard来做这个页面的过程,包括cell的点击事件都在storyboard里面完成,这里有一点需要注意,如果你的静态页面是使用tableview列表形式,那么控制器必须是tableviewcontroller,请看下面的gif-demo:

静态页面

简直是不可思议的快捷,如果这些我们使用代码写的话,我可以想想,单单switch代码你得写多少行?

什么?你用if - else?
呃......
或许你也需要一个计算器之类的?

Storyboard的多人开发问题

Xcode 7之前,storyboard在团队开发的时候非常不友好,因为一个storyboard文件有很多个界面,多人开发的话,就需要多个人都打开这个storyboard文件,而在Xcode 7之前你只要点开storyboard文件,虽然你啥都没有做,但是storyboard文件里面出现一个Modify标记,然后你提交代码的时候会conflict,很多团队因为这个问题放弃storyboard,转而使用xib,甚至连xib都不用了,直接手撸.

多可惜, storyboard的很多便捷特性都用不了了...

只从Xcode 7以后,苹果针对这个问题推出了storyboard reference来解决storyboard的多人开发问题

storyboard reference的功能就是拆分storyboard,把storyboard拆分成一个个小模块,这样每个开发人员对应一个模块,各自分离,互不相干

下面通过拆分tabbarcontroller的几个分支演示一下storyboard reference使用方法:

Storyboard Reference

通过上面的例子我们可以看到,storyboard referencetabbar拆成了2个module并分别放在两个storyboard里面,这样负责相应module开发的同学可以在自己的storyboard里面放心的开发,不用在担心提交时候的conflict

代码加载自定义的xib/Storyboard

在实际开发中,我们的视图跳转逻辑会很复杂,有些视图不能够通过链接segue直接跳转到,或者有时候segue已经链接了很多了,在链接下去,会显得很杂乱,这个时候你可以考虑使用代码加载xib/storyboard来完成跳转逻辑

非常简单,只有短短的几行代码

// 加载xib

// 这里返回的是一个数组,数组包含了改xib中所有的view,顺序跟你创建时的先后顺序一致
let xibView = Bundle.main.loadNibNamed("CustomXib", owner: self, options: nil)?.first
self.view.addSubview(xibView as! UIView)

// 加载storyboard
// 获取storyboard
let storyboard = UIStoryboard(name: "CustomStoryboard", bundle: Bundle.main)
let controller = storyboard.instantiateViewController(withIdentifier: "CustomStoryboard")

在使用代码加载storyboard中的控制器的时候,要注意在对应的storyboard

上要设置identity,方式如下图:

9EDC075E36FD7722B168934794DBE456.jpg

Interface Builder的一些问题

1.编译速度慢,这个问题是不可否认的,确实比纯代码的稍微慢一些,不过我觉得如果你使用的固态硬盘(SSD)的话,这个问题应该还好,笔者15寸MacBook Pro,平时编译速度还行,所以这方面的感受不太深

2.打包可能会增大包体积,这个问题在今天看来还能是个问题么 ?现在的App随便弄弄上MB了,增加个2-3MB的体积,根本没有啥感觉,最关键的是,现在WIFI的普及率太高了,4G流量也增加了,所以担心包体积的同学,可能还生活在5年前的2G3G时代

3.关于维护,有很多人说在维护xib/storyboard开发的项目的时候有些困难,找不到view关系,看不到代码等,其实我想说,那是因为你真的没有用心的去学习xib/storyboard,如果作为维护者的你也对xib/storyboard非常熟悉,你会发现维护xib/storyboard开发的项目会比纯代码的相对轻松的,至少你不需要在1000+的代码中去找某个button添加的action;同样的xib/storyboard中,你只需要右单击对应的button就能看到action的名称,直接定位到目标了

Conclusion

看完本文以后,大部分读者应该有一个猜测:作者一定是一个IB深度用户

好吧,我承认,从我学会使用IB之后的所有项目都使用了该技术,因为我觉得真的很好用,因为IB可以减少我们在界面上花费的时间,如此我们可以更好的专注于业务和结构设计等工作

同时也没有放弃代码,我也在使用Msonry,Snapkit和Neon,偶尔也会frame一下😝

其实IB有很多强大的功能本文并未提及和深入,比如@IBInspectable,Segue,Autolayout动画等

很多强大功能,大家可以自己去挖掘,网上的资源足矣

各种布局方法基本上都涉及了,大家根据自己的情况选择,还是那句话:适合自己的才是最好的

当然笔者还是推荐xib+storyboard😝
关于xib+storyboard或代码Autolayout欢迎大家一起讨论,如有错误及时指出及时修正

生命不息,折腾不止...
I'm not a real coder, but i love it so much!

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

推荐阅读更多精彩内容

  • 第三十九天,委以重任,不辱使命! 绿色出行三十九天,回京复命,走马上任,坚决将绿色出行进行到底,不辱使命,鞠躬尽...
    杨梵慈航阅读 275评论 0 0
  • 每次诞生 都只有一个意义就是和你在一起——五月天 一千个世纪 把美天说当作一份记录,记录我们,一千个世纪 who ...
    美天说阅读 402评论 0 1