我在开发中一直在使用Storyboard,代码中绝大多数的UIViewController是用Storyboard创建的。在这里分享一些我使用Storyboard的经验。
创建Storyboard
在项目中command+N,弹出如下页面,选中Storyboard然后点击Next给Storyboard命名,创建完成。
图1进入Storyboard文件,添加一个Scene。
图2设置Scene对应的类。如下图,在Custom Class填入类名。Identity中Storyboard ID的作用后面会讲。
图3加载Storyboard
如上图,代码中要获取RDHomeViewController在Storyboard中的这个实例,需要用到UIStoryboard中的API
#import <Foundation/Foundation.h>
#import <UIKit/UIKitDefines.h>
@class UIViewController;
NS_ASSUME_NONNULL_BEGIN
NS_CLASS_AVAILABLE_IOS(5_0) @interface UIStoryboard : NSObject {
}
+ (UIStoryboard *)storyboardWithName:(NSString *)name bundle:(nullable NSBundle *)storyboardBundleOrNil;
- (nullable __kindof UIViewController *)instantiateInitialViewController;
- (__kindof UIViewController *)instantiateViewControllerWithIdentifier:(NSString *)identifier;
@end
NS_ASSUME_NONNULL_END
代码如下
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Home" bundle:nil];
RDHomeViewController *homeViewController = (RDHomeViewController *)[storyboard instantiateViewControllerWithIdentifier:@"RDHomeViewController"];
此处的“Home”为Storyboard创建时的命名,Identifier为图3中的Storyboard ID。
UIStoryboard中的另一个方法
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Home" bundle:nil];
RDHomeViewController *homeViewController = (RDHomeViewController *)[storyboard instantiateInitialViewController];
此方法获取到的是勾选了下图所示项的UIViewController,一个Storyboard中只能有一个Initial View Controller。
图4
Storyboard中添加子视图
在RDHomeViewController中声明一个tableView属性,声明前添加IBOutlet,表示该对象是在Interface Builder中创建的。
@interface RDHomeViewController ()
@property (nonatomic, weak) IBOutlet UITableView *tableView;
@end
然后在Storyboard中添加一个Table View,并和tableView关联。
图5图5中黑色弹框为选中tableView点击右键弹出。这样就可以在代码中使用在Storyboard中创建的tableView。tableView的许多属性都可以在下图所示位置设置。
图6Autolayout自动布局
Storyboard中创建的子视图应该使用Autolayout进行布局。自动布局的核心是添加约束。
self.tableView.frame = CGRectMake(100, 100, 100, 100);
这种设置frame的方法使用的是绝对坐标系统,以父视图左上角为原点,每个子视图的x、y都是与原点之前的距离。
Autolayout使用相对坐标系统,通过设置约束确定子视图的位置和尺寸。
上图给tableView添加的约束为,充满屏幕除顶部状态栏和导航栏(如果存在)、底部tabBar(如果存在)之外的部分,无论是什么尺寸的屏幕,tableView都是显示在这部分区域。
图8通过上图的方式设置约束的constant,此时tableView距离屏幕左边缘50个点,距离屏幕右边缘100个点。
在Storyboard中添加约束可以帮助我们解决大部分屏幕适配的问题,但有些适配需要在代码中完成。比如图8中,在4.7-inch屏幕上,tableView与屏幕左边缘的距离为50,但是在5.5-inch屏幕上,我们希望这个距离为100。此时我们需要在代码中通过判断屏幕尺寸来设置此约束的constant。
@interface RDHomeViewController ()
@property (nonatomic, weak) IBOutlet UITableView *tableView;
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *tableViewLeftConstraint;
@end
每一个约束都是NSLayoutConstraint的实例,我们创建一个IBOutlet 修饰的tableViewLeftConstraint属性,然后与Storyboard中的约束关联。
图9self.tableViewLeftConstraint.constant = 10;
这样就可以在代码中设置self.tableViewLeftConstraint.constant的值了。
Storyboard中的UITableView
UITableView是我日常开发中使用最多的控件,大部分页面都可以用
tableView实现。下面介绍一下在Storyboard中使用tableView。
在之前的内容中,我们已经在RDHomeViewController中添加一个tableView。接着给tableView添加一个cell,在Storyboard中可以直接把cell拖入tableView。
图10设置cell对应的类。
RDHomeFirstTableCell是一个继承自UITableViewCell的类。
#import <UIKit/UIKit.h>
@interface RDHomeFirstTableCell : UITableViewCell
@end
图11
设置cell的Identifier。
图12Identifier是cell的唯一标识,在UITableViewDataSource的- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath方法中通过Identifier获取到对应的cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"RDHomeFirstTableCell";
RDHomeFirstTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
return cell;
}
cell添加子视图
在cell上添加一个label,并添加约束。
图13设置tableView代理
代理可以在代码中设置,也可以在Storyboard中设置。
图14配置cell子视图
根据面向对象的封装性,cell的子视图应该由cell自己来管理。即外部给cell一个model,cell显示相应的内容。为了更好的体现封装性,除需要在外部使用的属性外,属性声明应该写在匿名类别(即类扩展)中。
@interface RDHomeFirstTableCell ()
@property (nonatomic, weak) IBOutlet UILabel *labelTitle;
@end
图15
将label与labelTitle关联,便可在代码中设置label。按照上述方法再添加一个labelDetail。可以在Storyboard中设置label的text,也可以在代码中设置。
- (void)awakeFromNib {
[super awakeFromNib];
self.labelTitle.text = @"我是标题";
self.labelDetail.text = @"我是详情";
}
在Storyboard中和awakeFromNib方法中设置的是固定的标题,如果需要动态设置,需要添加一个public方法
- (void)configureWithModel:(RDHomeFirstCellModel *)cellModel {
self.labelTitle.text = cellModel.titleText;
self.labelDetail.text = cellModel.detailText;
}
在此处调用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"RDHomeFirstTableCell";
RDHomeFirstTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
[cell configureWithModel:self.dataSource[indexPath.row]];
return cell;
}
这样写就是cell自己管理子视图,外部只需要告诉它需要显示的内容,减少了ViewController中的代码量。
如果cell上有action,可以通过delegate回调ViewController。
#import <UIKit/UIKit.h>
@class RDHomeFirstCellModel;
@protocol RDHomeFirstTableCellDelegate;
@interface RDHomeFirstTableCell : UITableViewCell
@property (nonatomic, weak) id <RDHomeFirstTableCellDelegate> delegate;
- (void)configureWithModel:(RDHomeFirstCellModel *)cellModel;
@end
@protocol RDHomeFirstTableCellDelegate <NSObject>
- (void)firstTableCell:(RDHomeFirstTableCell *)firstTableCell didClickButtonWithModel:(RDHomeFirstCellModel *)cellModel;
@end
- (IBAction)buttonClick:(id)sender {
if ([self.delegate respondsToSelector:@selector(firstTableCell:didClickButtonWithModel:)]) {
[self.delegate firstTableCell:self didClickButtonWithModel:self.cellModel];
}
}
cell上添加一个button,关联点击事件。
图16关于Storyboard合作开发的问题
将Storyboard按模块和功能进行拆分,每个Storyboard只由一个开发者负责,Storyboard之间使用UIStoryboard中的API建立连接,可以尽可能的避免合作开发的问题。
小结
以上就是在日常开发中Storyboard的基本用法,由于笔者的水平有限,如有不足之处,欢迎交流指正。
网友评论