xib、stoaryboard详解

来源:互联网 发布:iphone照片传到电脑mac 编辑:程序博客网 时间:2024/05/17 01:56

一、xib、sb简介

1、xib执行效率的确没有代码效率高,因为加载要多一步——把xib文件加载到内存中

2、SB还会省去很多页面跳转之间的胶水代码(segue),甚至不用写代码就能实现在各个页面中切换,tableView的cell可以直接拖到tableView里,可以给tableView添加header、footer,可以添加手势、设置代理、size classes使得适配变得更加容易、xib也使得国际化变得很容易、可以通过代码给xib动态加入属性...这些东西,有些根本不用写代码,有些只需写极少量代码就能实现。


二、xib文件的使用

1.基于UIViewController子类的xib的使用

这种情况下使用很简单,对VC直接alloc,init就可以,VC会自动去找自己对应的xib文件,即使我们自定义了一些init方法,也不需要对加载他的xib做处理,系统会自动帮我们找是否有与其对应的xib文件,例如我们有这样一个初始化方法:

- (instancetype)initWithCustemData:(id)aData;

我们在创建VC实例的时候可以直接调用这个函数,不用理会xib文件的问题,我们的父类在初始化的时候去自动帮我们找与之对应的xib文件,那么问题来了,父类怎么知道我有没有xib文件呢?是这样,父类会判断有没有和我们这个要初始化的VC相同名字的xib文件,如果有就会加载该xib文件,如果没有,父类就认为我们该VC没有xib文件,就会走正常的init方法。

加载xib的init方法是:

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;

如果一旦我们的VC类的名字与对应的xib文件名字不同的时候,我们就必须调用这个初始化方法来创建VC实例了;

[[ViewController alloc] initWithNibName:@"xxx" bundle:[NSBundle mainBundle]]

xxx的地方填那个与我们VC类名不同的那个xib文件的名字,不过一般情况下,我们的VC都与xib保持相同的名字,这里只是想说明,如果名字不同也是可以的。

2.基于UIView子类的xib的使用

TestView *tView = [[NSBundle mainBundle] loadNibNamed:@"TestView" owner:self options:nil][0];

上述代码再次说明xib文件是资源文件,放在main bundle中,@"TestView"是xib文件的名称,后面两个参数暂时不用了解,就固定传self和nil就行,值得说的是,loadNibNamed: owner: options方法返回的是一个数组,而不直接是对象,这是考虑到了Mac开发会有多个对象返回的情况,在iOS开发中就只有一个,固定取[0]就行。

注:一般的UIView对象,代码初始化的时候都会调用initWithFrame:方法,但是用xib创建的UIView对象是不会调用此方法的,因为该对象的Frame在xib文件中就可以确定了。以xib的形式保存控件对象的过程其实叫做固化(archive),通过xib文件创建控件的过程叫做解固(unarchive),固化是iOS持久化的一种比较好的解决方案,以后有机会会说说iOS持久化的各种方式的优劣,这里不再深入,而与固化相关的初始化函数是:

- (instancetype)initWithCoder:(NSCoder *)aDecoder

所以,当以xib创建UIView对象的时候这个函数会调用,之前在initWithFrame:中要做的事情,可以放在initWithCoder:中,或者放在:

- (void)awakeFromNib

{

    [super awakeFromNib];

    //...

}

该函数会在initWithCoder:后调用,从名字我们就能看出,这个函数的触发时机是控件已经从xib文件中“解固”之后,两个函数之间的关系有点像VC的loadView和viewDidLoad之间的关系。


三、SB文件的使用

由于SB文件与VC一般是一对多的关系,所以我们不仅要知道即将创建的这个VC的实例对象是加载的哪个SB,而且还要知道加载的是该SB中的哪个具体的VC

SecVC *secVC = [[UIStoryboard storyboardWithName:@"Demo" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"SecVC"];

@"Demo"参数代表SB的文件名字,@"SecVC"代表该VCSB中的ID,具体使用会在下面的文章中提到。个人建议,该ID最好与类名相同,这样便于将实例化的方法封在基类中。


四、xib布局系统详解

1、xib右侧标签说明

第一个标签(一页纸):

主要介绍了xib文件的一些基本信息,一般不用修改。是否开启auto layoutsize classeslaunch screen等功能在interface builder document菜单下,Localization菜单是与国际化相关的,这几个功能以后会详细介绍。

第二个标签(问号)

是一些帮助说明之类的东西,可以忽略,应用中不会用到

第三个标签(文档)

1)、该标签中最经常用到的就是Custom Class菜单中的Class,一般就是将你的类与xib文件挂钩

2)、User Define Runtime Attribute是一个不经常用,但是很有用的功能。

3)、其他菜单都不常用,其中Accessibility是支持给残疾人开发更利于他们使用的app的时候用到的。

第四个标签(尖角向下五边形)

这个是属性标签,xib中最重要的一个标签,对应的是一个控件的可设置的一些属性,根据你选择的控件不同,属性标签中呈现出来的东西也是不同的。

1)、设置控件的颜色属性时通过Opacity改变颜色的透明度。注意:改变颜色的透明度有时候很重要,它与直接改变viewalpha产生的效果不同,改变viewalpha,它的subViewalpha也会改变,而改变颜色的alpha,是不影响自己的subView的。

2)、UIButton等控件是有不同状态的:DefaultHighlightedSelectedDisabled,可以针对不同的状态设置不同的属性值,还可以设置shadowedge等属性,十分方便。

第五个标签(竖着的尺子)

这是与控件frame相关的标签,该标签里的属性随着控件类型不同而变化,最典型的例子是Cell,是否开启auto layout也会影响该标签中的属性,但一般常用的就是设置xyWidthHeight,我们给一个控件添加的layout约束也都在这里显示。

第六个标签(向右的箭头)

该标签主要负责xib文件和类的源文件交互用,都是连线相关的操作,例如UITableViewdelegatedatasoutceIBActionIBOutlet等相关,如果我们用了xib的页面产生了莫名其妙的crash那你就要看看是不是自己的连线有问题了。


2、中间右下角标签说明:

第一个图标(文档中间一个向下的箭头)

这是xcode7在iOS9中新加入的功能——stack view,相当于一个容器view用来统一管理他所有subView的约束,其实普通的UIView也可以作为容器view来管理其subView的约束,我们之前做复杂UI显示逻辑的时候往往也会放一个背景的容器view,stack view就是起到这个作用,意义不是很大,它做的事情UIView也可以做,但是他的优势在于:可以通过设置属性的方式让系统自动添加对其subView的约束,而且该view是不渲染在页面上的,对它设置背景色等属性是无效的。

第二个图标(左侧竖线)

用于添加多个控件间对齐关系,从上到下依次是左对齐、右对齐、上对齐、下对齐、水平对齐、竖直对齐,这些现在都是灰色的不能选择,只有同时选中多个控件,他们才是可用的,或者先选择一个控件,然后按住control拖动到另一个控件上,就会弹出一个控件对齐的窗口,可以在里面设置两个控件的对齐关系。下面两个是相对于superView设置水平、竖直居中,选中单个控件就可以设置

第三个图标(两条竖线中间方框)

用于对单个控件设置约束,说明如下:

1)、上面的四个框分别填写上下左右的约束。

2)、Constrain to margins选项的解释:当拖动一个控件到另一个控件里时,作为super的控件会有几条参考线(蓝色虚线),上下左右四个方向的边缘会有,水平、竖直的中心处会有。若勾选Constrain to margins实际super与sub之间的参考边缘就是这些参考线,而不是实际的super的frame的边缘,如果我们不勾选的话就是以frame的边缘为参考。

3)、Equal Widths、Equal Heights是与其他控件保持相同的宽高,默认是灰色不可用状态,只有选择两个以上的控件,才可使设置,同样也可以先选中一个控件,按住control拖动,弹出的窗口中也有该选项。

4)、Aspect Ratio 是设置自身的宽高比的。

5)、Align选项同样是设置两个以上控件的对齐关系的。

6)、Update Frames一般是用来更新frame的。我们设置的约束如果与当前控件的frame产生冲突的时候就要解决冲突,要么修改约束,要么修改frame,最后使系统可以没有歧义的确定UI布局。

第四个图标(两条竖线中间正三角)

1)、该图标就是为了解决冲突而产生的,以更新frame,添加约束、删除约束等等方式去解决frame与约束间的冲突。

2)、当你设置约束发生错误,或者不知道怎么设置的时候Clear Constraints会清除所有约束,让你重新设置。


3、使用auto layout为什么代码修改frame不生效?

不使用auto layout时是可以在viewDidLoad:里设置frame的,一旦开启了auto layout,就要注意,通常在viewDidLoad:中设置frame就不再生效,因为iOS5加入的viewWillLayoutSubviews会在viewDidLoad之后调用,而该函数会在执行的时候去加载该文件对应的xib设置的约束,就是说在viewDidLoad:中设置frame的时机太早了,没有生效就又改成了xib中的样子,而且在viewWillLayoutSubviews中修改frame也是不生效的,那么,如何才能用代码修改布局生效?

方法一:

viewDidLayoutSubviews中修改frame,这是最简单的方法。

方法二:

选中xib中的约束(蓝色线段,左边栏Constants里的item)像拖动其他控件一样,将其拖动成为IBOutlet的属性或全局变量


4、Files Owner。

Files Owner指这个xib文件的所属文件是谁,简单的说是xib文件和谁建立起交互,用户通与该xib呈现的页面进行交互的时候,谁来处理背后的逻辑。具体来讲xib文件能拖动“连线”到哪个源文件中去建立IBAction、IBOutlet、delegate、datasource等。

一般基于View创建的xib的Files Owner都指定为一个VC==我说不用!。基于VC创建的xib,创建的时候系统就已经把该xib文件的Files Owner指向了该VC,一般这种情况就不对Files Owner做修改了。


5、Files Owner的应用举例

有这样一个场景,VC中有一个textfield要设置inputAccessoryView属性,该属性的view显示起来很复杂,有多个按钮,每个按钮对应不同的事件。

一般的做法是用代码写一个这样的view赋值给inputAccessoryView属性,其实这个例子可以用xib实现的更优雅,不用写代码就可以完成(当然点击每个按钮后的事件处理代码是要自己写的)。

例子中要考虑的重点是:如果创建了一个AccessoryView.xib去拖出这样一个view,虽然不用“画”UI了,但是我们要建一个AccessoryView.h、AccessoryView.m类去与xib文件对应,在AccessoryView.m中把它上面的按钮事件记录下来,一旦触发事件,要通过delegate或通知等其他形式把事件从AccessoryView类传递给VC类,这样使事情更加的麻烦了,如何解决?

有人会想:创建AccessoryView.h、AccessoryView.m是没有必要的,因为他们除了传递事件,根本没做任何事情,这样的话就不创建他们,只有AccessoryView.xib文件,然后把xib中的按钮分别拖动到VC类中建立起IBAction的“连线”关系,事情就搞定了。

这个思路很好,但是我们会发现,并不能实现AccessoryView.xib与VC中的“连线”,因为VC类根本不认识这个xib,因此该VC是不允许这个xib通过“连线”向它内部添加代码的,如何解决这个问题?——Files Owner!

将AccessoryView.xib的Files Owner指定成该VC的类,此时再拖“连线”到VC就可以了,这样xib中按钮的事件就能直接回调到VC中我们设置的方法里了。

这是解决这个问题最简单的方法,不用写一行代码,他给我们的启示是:xib文件是可以不依托于UIView子类、UIViewController子类单独使用的,只是这种情况比较少见,这是一个例子。

这个例子稍稍变一下需求,就是我们在一天一点xib:4简单使用xib里谈论Files Owner要说的例子了,如果点击textField的AccessoryView会有UI上的变化,或者交互的话,最好就是要创建AccessoryView.h、AccessoryView.m,然后把AccessoryView.xib分别与AccessoryView.m和VC的.m连线,UI的处理在AccessoryView.m中完成,逻辑的处理在VC的.m中完成,这样的“双连线”很好的解决了要把事件从AccessoryView.m传递给VC的.m的问题。


6、IBAction与IBOutlet

这是我们最常接触的两个,IBAction与IBOutlet分别标识方法与属性,它们标识着由它们修饰的方法和属性是来自xib的,我猜它们是给编译器看的。


7、IBOutletCollection(ClassName):

将基于IBOutlet创建的对象放在一个NSarray里。

@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *testViewArr;

创建了一个array,里面放的是用IBOutlet创建的UIView.

注意最好用strong进行修饰,而且如果你声明的不是NSArray,即便是UIColor,系统也不会报错,你打印这个color发现,系统用的还是NSArray。这个array的顺序是连线时候的顺序,但是不排除不同版本的xcode会改变这个顺序,所以最好不要依赖这个顺序。


8、IBInspectable

在OC中使用IBInspectable,在swift中使用@IBInspectable

它是xcode6引入的新功能,它修饰的属性或者实例变量,会显示在xib中的属性栏中(Show the Attributes inspector),我们之前讲的东西都是xib是如何影响代码的,而IBInspectable是用代码影响xib的,可能我的表述不是很正确,还是看一个具体例子吧。

@interface ViewController: UIViewController


//gj_testFlag用IBInspectable修饰后,就能在xib中看到这个属性了,当然也可以用xib进行赋值了

@property (assign, nonatomic) IBInspectable BOOL gj_testFlag;


@end

9、IB_DESIGNABLE

在OC中将IB_DESIGNABLE写在@implementation前,在swift中将@IBDesignable写在class前,它也是xcode6引入的新功能,它的作用是可以在不运行的情况下把你的代码显示在xib或SB文件中。两点说明:

1)、这是一个针对UI显示的功能,所以只能是在UIView及其子类或者NSView及其子类上生效。

2)、要想使IBDesignable起作用必须把代码写在drawRect里才能显示,同样的代码,我写在了awakeFromNib里就不会再xib中看出效果,只有写在了drawRect才可以。


10、xib的国际化

参考网址http://www.jianshu.com/p/6b05c05bfc39。


11、User Define Runtime Attribute

把一些xib中不能设置的属性,写在这里,就可以了,弄个圆角矩形的button再也不用写代码了,很方便。


12、Object

这是一个更高冷的xib用法,相对来说比较复杂,我们一点点的开始。

1)、我们先建立一个Person类,继承自NSObject。

2)、在xib或SB文件右边栏中找到Object这个对象,拖动它到左侧边栏中,把Object对象的class设置为Person!

3)、将object的引用添加到VC中

#import "Person.h"

@interface ViewController : UIViewController

@property (nonatomic,strong) IBOutlet Person *aPerson;

@end

4)、在xib或SB文件右边栏中拖入一个button

5)、把这个button的响应函数拉到Person类中,命名为sayHello:函数。

- (IBAction)sayHello:(id)sender {

    NSLog(@"hello person");

}

6)、运行测试。

我个人认为,这种用法其实意义很大:

1)、你如果遇到这种Controller需要其他类处理的情况,xib也是可以应用的,而且还是不用写什么代码,设置什么delegate,相当的方便,如果你不知道如何应用,只能说明,你之前没有想到过xib会如此的强大,可以做如此多的事情。

2)、我们都熟悉Category,知道他的一个优点就是把不同的实现可以分散到不同文件中,或者把相同的功能放在一个Category中管理,更清晰,就像苹果自身的代码那样,你跳进UIViewController中看看,虽然是一个文件,但是不同功能的实现基本上都是在不同的Category中的,这给我们一个思路,如果一个VC交互复杂、UI上又很多button有很多跳转,我们完全可以用这种方式把耦合性地的部分抽离出来,或把相同功能或业务的部分抽离出来,独立封装成类,这样就可以避免几千行代码的超级VC类的出现,当大家在争着解决臃肿VC的问题而提出的各种五花八门的设计模式的时候,我想这也是一个不错的解决方案(以后会对MVC、MVP、MVVM、MVCS等各种模式提出一些自己的看法)。说到解决方案,如果有看过《Objective-C编程之道——iOS设计模式解析》这本书的朋友会知道,里面为了解决页面跳转混乱、交互复杂的情况引入了中介者模式,个人认为,充当终结者最好是一个NSObject类的子类,而xib的这种用法是中介者模式的一个很好的应用。

13、iOS9中关于xib的一些新特性

UIStackView和storyboard reference,请参考http://www.jianshu.com/p/63e5078ca9e0


14、xib的原理、优化

请参考http://www.jianshu.com/p/2f9e71ef7f52。

五、sb使用详解

1、sb为什么比xib强大了?

xib可以基于View、VC甚至自己独立的使用,而SB只能基于VC使用,为什么说比xib更加强大呢?主要是下面的两个原因:

1).SB支持segue

2).SB对cell的支持更加强大


2、什么是segue?

在一个VC中选择要发生跳转的按钮,按control拖动到另一个VC上就会出现一个菜单,在菜单上你就可以选择跳转的方式push、present,这样不用写一行代码就能完成页面间的跳转,而两个VC之间的像纽扣一样用线连着两个VC的的东西就是segue,是一个UIStoryboardSegue对象,我们可以简单的理解成是完成页面跳转相关功能的一个类。

3、segue虽然简单,如何传参?

假设我们要点击ViewController这个VC里的一个按钮,跳转到SecVC这个VC中,把testTitle这个参数传过去。第一步还是要选中按钮,拖到SecVC里,然后选中这个segue,给它一个id值,确保用代码可以找到它,然后在ViewController中编码:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

{

    if ([segue.identifier isEqualToString:@"sec"]) {

        SecVC *secVC = (SecVC *)segue.destinationViewController;

        secVC.testTite = @"hello world";

    }

}

id类型的sender参数就是触发跳转的那个button或其他控件。


4、不确定的跳转如何使用segue?

我们假设我们要点击ViewController这个VC里的一个按钮,有可能要跳到SecVC,有可能要跳thirdVC。

1).在SB中准备segue。

注意我们这次创建的segue的方式并不是选中button按control拖到另一个VC中,而是选中VC,右键选中triggered segues里manual后面的+分别拖动到另两个VC里,这个很重要,而且还是要给segue一个id。

2)、编码实现跳转:

- (IBAction)testSegue:(id)sender

{

    BOOL flag = NO;

    if (flag) {

        [self performSegueWithIdentifier:@"sec" sender:sender];

    } else {

        [self performSegueWithIdentifier:@"third" sender:sender];

    }

}

在segue跳转页面的过程中还会调一个函数,我们可以重写这个函数,来决定是否让这个跳转发生。eg:

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender

{

    if (_islogin) {

        return YES;

    } else {

        NSLog(@"您还没有登录,请先登录");//用log举个例子

        return NO;

    }

}


5、更好的使用Cell

用xib的时候,要想用cell,通常是建立一个cell子类,一个与之对应的.xib文件。用SB的时候要想用cell就简单的多了,因为cell是可以直接拖动到tableView里的,直接在tableView里管理cell设置属性,当然如果是复杂的cell还是要子类话对应的.h、.m的。SB在使用cell分两种情况:1静态cell,2动态cell。

静态cell

设置静态cell:

注意静态cell一定要基于UITableViewController,否则会报错。

展示静态cell的tableView是不用调用自己必须实现的datasource协议的你会很惊讶,为什么必须调用的datasource协议都不用实现?答案是静态,静态的cell,意义就在于你在xib中设置成什么样,他就展现什么样,不会再调用datasource向你要cell了,因为在SB文件中已经确定下来了。

在做一些“死”页面的时候SB的静态cell是很好的选择,静态cell也不是什么都不能做,静态cell里的button还是可以拖到@implementation中形成IBAction的,但是是无法生成IBOutlet属性或字段的。

即使你强行的给一个静态的cell指定了一个cell的类,也是无法向其内部拖入IBOutlet的。这就是静态cell的局限性,但是如果你要设置的数据不多还是可以考虑用静态cell,因为你可以通过给cell上的控件设tag来找到它从而赋值。

动态cell的使用

设置TableView的content属性为Dynamic Prototypes就可以使用动态cell。使用动态cell的话就要在VC中实现那两个必须实现的datasource协议,下面举个简单的例子:

1.给cell指定一个class,如TestCell

2.给cell设置identifier,重用机制使用

3.给cell添加一些控件,并拖到TestCell源文件中生成IBOutlet的属性,在VC中实现那两个必须实现的协议方法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return20;

}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    TestCell *testCell = (TestCell *)[tableView dequeueReusableCellWithIdentifier:@"testCell"];

    testCell.testLabel.text = @"hello world";

    return testCell;

}

这里注意,不需要在tableView:cellForRowAtIndexPath:中创建cell了,这是与xib使用cell最大的区别,这是很重要的,千万记住,因为我们是直接拖cell到SB文件的tableView里的,所以直接从重用池里取就好了。

这里稍微提一个地方:我们这里取重用cell用的是函数:dequeueReusableCellWithIdentifier:,而系统中还有另一个重用的函数:dequeueReusableCellWithIdentifier:forIndexPath:这个函数比我们代码中用的那个好,是我们用的那个api的优化版,它是iOS6引入的api,完全符合我们一般app对版本兼容的要求。


6、添加tableHeaderView和tableFooterVie

不用代码,“拖”出header于footer,其实很简单,选中tablView,在控件中找到View拖到tableView上,往最上方拖动,直到看到左右有两个圈的时候松手,这个View就是tableHeaderView了,同理,往最下方拖,就是tableFooterView。


7、LaunchScreen.storyboard

为了更好、更方面的配置启动图,LaunchScreen.storyboard出现了,简单来说,启动的时候会加载这个SB文件,我们可以同过它更方便的设置启动图,可以用auto layout减少启动图数量的使用,但此功能只支持iOS8及其以上的系统。

那么问题来了,我要想适配更低的系统怎么办?答:不用。

如何禁止该功能?点击工程->General->Launch Images Source


8、size classes

size classes也是iOS8的新功能,.xib文件也是可以使用的,但大部分情况还是基于SB来使用,从xcode6开始我们新建的xib或SB文件中对应的View变成了正方形,而且下方显示"wAny hAny",点击后发现是可以选择的,选择不同的情况,View又变成了不同的形状,这就是size classes。简单的理解:size classes就是对设备的屏幕尺寸进行了抽象,宽高都分别用Regular、Compact来表示,我们其实不用太在意名称,只知道,以后不同的设备或者不同的状态(横竖屏)可以由这种描述来表示即可,具体的表示如下:

iPhone4S,iPhone5/5s,iPhone6,iPhone6s

竖屏:(w:Compact h:Regular)

横屏:(w:Compact h:Compact)

iPhone6P、iPhone6sP

竖屏:(w:Compact h:Regular)

横屏:(w:Regular h:Compact)

iPad

竖屏:(w:Regular h:Regular)

横屏:(w:Regular h:Regular)

由此我们可以看出,虽然把屏幕抽象了,但是用Regular、Compact并不能标识出各种不同的设备,竖屏情况下,所有iPhone用这种抽象表示是一样的(w:Compact h:Regular) ,所以:如果我们不适配横屏,或者iPad,只做竖屏iPhone显示的话,size classes并没有什么用,我个人认为目前的size classes应用面还是很窄的

如果我们适配横屏,或iPad应该怎么做?因为横屏后或者设备是iPad,那么size classes这种描述就会发生改变,而一旦描述发生改变,我们就能根据不同描述,做不同的布局,例如:我们在w:Compact h:Regular的情况下向View里拖了一个label,此时我们改变size classes为w:Regular h:Regular,你会发现该label不显示了,也就是说你在w:Compact h:Regular的情况下显示的label只在该情况下显示,这样就可以针对不同类型的描述,设置不同的UI布局了,除此之外,还可以给控件添加适配不同类型的描述。开启size classes功能后,选中控件的属性标签会发现,有些属性前有“+”的标志,点击这个标志就可以给这个控件的这个属性添加不同类型的描述了。


9、Storyboard Entry Point

如果我们用xcode6或者更高版本的xcode创建工程的话,你会发现自动就有了一个Main.storyboard,此时application:didFinishLaunchingWithOptions:中没有一行代码运行就没有问题,并不像之前那样,要创建window,指定rootViewController,这些是如何实现的?在项目的General->Main Interface路径下默认设置的就是Main。

那么问题来了:一个SB是可以对应多个VC的,他选哪个VC作为window的rootViewController?答案是Storyboard Entry Point,这个东西就是用来指定那个作为rootViewController的,也就是说,xcode会找到表示为Storyboard Entry Point的那个VC加载它成为rootViewController,那么如何来指定Storyboard Entry Point?勾选vc的Is Initial View Controller就是设置了Storyboard Entry Point,设置了Storyboard Entry Point的VC会有一个向右的箭头指向它,注意你在Main Interface里选的SB文件中一定要有VC勾选了这个,不然xcode是不知道如何设置rootViewController的,你不用担心多选的问题,你如果选择一个新的VC,旧的那个VC就自然没有了Storyboard Entry Point,但是如果你又取消了勾选那么旧的VC并不会自动又添加Storyboard Entry Point的,要小心。







0 0
原创粉丝点击