参考文档1
参考文档2
近来系统学习了《利用IB优雅地开发UI》,收获颇多。开始接触可视化编程时,非常喜欢,简单高效,可以少写很多代码,但后来实践中更多的使用的是纯代码。主要还是对XIB及SB好些特性不甚了解,自以为代码处理问题更加灵活,也更有优越感,实则不然。
IB 从Xcode4开始集成到Xcode当中,成为IDE的一部分,之后每个版本都会对其进行改进加强,更多地体现出苹果对可视化编程的重视。
IB 基础
Xib 是 .xib 扩展名的文本文件,以 XML 形式存储 UI 相关数据。xib可以不依赖源文件,无须与源文件“关联”,单独存在。
SB 是 storyboard 扩展名的文本文件,以 XML 形式存储 UI 相关数据。
xib 和 SB 经过编译后生成nib 文件和storyboardc文件
SB可以理解成一组与ViewController关联的xib集合,该集合中同样保留着各VC间的跳转数据-
xib与源文件关联,文件名称可以不相同,因为关联的其实是类,而非文件本身。
创建VC时可以同步创建xib,但若不同步创建时需要特别注意:
A方法:先创建空模板,拖入View,将其 File Owner 的Identity 设为对应的类,注意:还要将该VC的view与控件显示区的view边线关联。
B方法:直接在空模板中创建VC,将其Identity设为对应的类,同时用nib加载(本质也说明一个xib可以创建多个UIView子类,或一个VC)。
UINib *nib = [UINib nibWithNibName:@"VCname" bundle:nil] ;
VCname *vc = [nib instantiateWithOwner:nil options:nil ][0];
- storyBoard 通过下面实例方法加载相应的VC
[sb instantiateInitialViewController];(左侧小箭头的初始VC)
[sb instantiateViewControllerWithIdentifier:@"VCName"];
相比较xib,sb不仅可以处理VC间跳转,同时对TableView等视图可以直接拖入Cell进行布局,xib更倾向view层面,场景复杂,sb更倾向VC层面,功能强大。
Xib相关
- nib文件加载的两各方法
[[NSBundle mainBundle] loadNibNamed:@"" owner:nil options:nil];
[[UINib nibWithNibName:@"" bundle:nil]instantiateWithOwner:nil options:nil];
TableView自定义Cell时,控件是添加到它的contentView上,不能直接添加到cell上,否则某些情况下就会出问题,如左滑删除cell时。
TableView中Cell 有重用机制,当从重用池中取出cell时,上面的数据并没有清空,再次赋值时遗漏某个控件则可能出现显示错误。
可以实现下面的方法,将cell上控件赋值清空。
- (void)prepareForReuse;
使用Asset Catalog 完全不用关心图片名称,也不用按照@1x,@2x和@3x 的规则命名,不用关心存储位置,便于管理图片。
-
AutoLayout 本质上也是用唯一性来设置视图的frame。当view使用了AutoLayout布局,系统会在layoutSubViews时加载它的约束信息。所以如果在viewDidLoad中设置frame起作用后,紧接着执行layoutSubViews中就被覆盖掉了。
可以将控件的某个约束拉成属性,并动态调整改变。
[UIView animateWithDuration:0.2 animations:^{
self.widthConstraint.constant = 100;
self.heightConstraint.constant = 100;
//添加动画
[self.view layoutIfNeeded];
}];
在iOS 9之后推出的UIStackView容器视图,在有些布局中特别高效,但由于版本的限制,所以可以用ForkingDog团队开源的FDStackView,在低版本中使用高版本控件。只要在Show the File inspector中将Builds for 指琛为最高的iOS 版本
-
UIScrollView 属于AutoLayout 中的异类,约束稍显麻烦。
在给视图添加相对于父View的约束时,并不是以父view的尺寸为依据,而是以父view内容的尺寸为依据。
普通View 不能滚动,所以它的尺寸就是内容尺寸。
ScrollView的内容尺寸也就是其contentSize必须确定,才能准确布局子视图。
一般有两种处理方式:- 给scrollView添加一个容器View,给容器view添加必要的约束以确定contentSize。再向容器视图中添加子视图即可。
- 直接给scrollView添加子视图,但需要添加足够的约束,以保证可以唯一的确定它的contentSize.
XIB 嵌套
关于xib的嵌套使用在父视图-awakeFromNib方法中用代码添加子视图,但不优雅。
- 同时ScrollowView重写方法
//设置frame时用self的尺寸,而不要随意设置(很重要)
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
UIView *containerView = [[[UINib nibWithNibName:@"ScrollowView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, 179);
containerView.frame = newFrame;
[self addSubview:containerView];
}
return self;
}
- 在父视图上添加UIView,设置其类型为ScrollowView
Storyboard
在IB 中选择View Controller,在 Show the Size inspector 中有 Simulated Size标签,该标签两个值:Fixed 和 Freeform 。可以设置为Freeform改变VC的Size,它影响在IB 中的显示 。这在ScrollView布局中非常有用。
segue 进行页面间跳转很方便,segue可以设置Identifier
//跳转时传值
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:@"showSec"]) {
if ([segue.destinationViewController isKindOfClass:[SecondViewController class]] && [segue.sourceViewController isKindOfClass:[ViewController class]]) {
SecondViewController *secVC = segue.destinationViewController;
//传值建议用KVC
//不能直接对目标VC的UI进行更改
[secVC setValue:@"不是测试" forKey:@"secStr"];
}
}
}
//根据相应条件判断是否要跳转
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if ([identifier isEqualToString:@"showSec"]) {
//根据condition判断
//返回值决定是否跳转
return YES;
}else{
return NO;
}
}
回传时须要在要返回去的VC中实现(IBAction)方法,参数UIStoryboard。将返回事件联接到VC 顶部的Exit按钮上,会自动选择方法。
//回传值的方法
- (IBAction)theBack:(UIStoryboardSegue*)segue{
//注意
//返回值必须是 IBAction
//方法名随意
if ([segue.identifier isEqualToString:@"backViewController"]) {
//destinationViewController 是当前VC
NSLog(@"%@",[segue.destinationViewController class]);
//sourceViewController 是源VC
SecondViewController *secVC = segue.sourceViewController;
NSLog(@"%@",secVC.backStr);
}
}
- 用代码将一个VC中的视图添加到另一个VC中时,通常会这样写
[self.view addSubView:subView];
[self addChildViewController:suVC];
在IB 中系统提供一个ContainerView 专门处理这类事件,需要时可尝试使用。
- TableView中点击cell 或 accessory 跳转,为方便传值,可以实现
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
UITableViewCell *cell = sender;
NSIndexPath *index = [self.tableView indexPathForCell:cell];
}
- SB 中有需要设置静太Cell 的需求,cell 样式简单,数量较少。则可以通过创建 TableViewController ,选中TableView 在 show the Attributes inspector 中将Content标签设置成Static Cells即可。
代理方法都不用写,cell 的 identifire 也不用设置
TableViewController可以与源文件关联,但静态Cell 不可有关联源文件,只能是UITableViewCell
- SB中CollectionView 设置headView 和footerView 除了勾选 SectionHeader 和 SectionFooter ,还要实现代理方法
- (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
if (kind == UICollectionElementKindSectionHeader) {
return [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"Header" forIndexPath:indexPath];
}else{
return [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"Footer" forIndexPath:indexPath];
}
}
Use Trait Variations适配
通过底部的Trait Variations 特征亦是适配横竖屏下的不同约束。
点击相应约束,constant侧面有“+”标志的可以增加以添加不同特征约束。
设备 | 竖屏 | 横屏 |
---|---|---|
3.5英寸 iPhone | wC hR | wC hC |
4.0英寸 iPhone | wC hR | wC hC |
4.7英寸 iPhone | wC hR | wC hC |
5.5英寸 iPhone | wC hR | wR hC |
iPad(all) | wR hR | wR hR |
External Object 相关
仅限 xib 使用
- 如图示做以下操作,并在 VC,VCView 和 VCManager 中拉相关点击事件。
- 在 VC 中代码
- (void)viewDidLoad {
[super viewDidLoad];
_manager = [VCManager new];
//这里注意传入这个dic中的_aPersonHandle对象必须是全局变量
NSDictionary *pramaDic = @{@"testKey" : _manager};
NSDictionary *optionDic = @{UINibExternalObjects : pramaDic};
//options这个参数只接收一个key为UINibExternalObjects的字典,
//这个字典的value也必须是个字典,
//这个value字典的key就是xib中设置的external object的identifier
VCView *vcView = [[NSBundle mainBundle] loadNibNamed:@"VCView" owner:self options:optionDic][0];
[self.view addSubview:vcView];
}
Object [1]
- 新建ViewControllerManager 类,继承自NSObject,并实现 -(IBAction)***方法。
- IB中添加Object类,并且与源文件关联。
- 通过连线可以在ViewControllerManager中实现VC中部分代码逻辑。
- 可以将viewControllerManager作为属性拉到VC中去,也可以将VC作为属性拉到ViewControllermanager中去
须要用weak修饰一下,防止循环引用 )
VC瘦身的话可以考虑将VC中的Delegate中的内容放到Manager中去
-
实测仅SB成功 ↩