自定义视图
自定义视图是一种封装的方法
通常一些复杂的界面都是由一些常用的控件组合构成的, 将这些组合提取出来.写成一个组合控件, 在完成复杂界面的时候, 就可以通过组合控件简化界面的逻辑.这个组合控件就称为自定义视图.
自定义视图的优劣取决于它的复用性. 自定义视图的复用性越高, 则它的质量就越高. 一个高质量的自定义视图, 可以让程序员在铺设界面的时候极大的缩减代码量和时间.
如何完成一个自定义视图
这里举个简单的例子.
比如一个常见的注册界面.
除了button, 通常而言都是 一个label对应一个textField, 且两者是在同一行上的.
这时候如果对每个控件都算坐标和是不是非常的麻烦?
通常而言, label好写, 但是它对应的textField坐标计算起来就十分的复杂了. 需要考虑到label和textField横向的间距, 需要考虑到label和textField两侧对应的留白空隙大小的对称. 这非常复杂.
比如这样的一个界面:
通常连续3对或者以上的label和textField的坐标已经非常复杂了, 如果计算坐标还是用数字, 在改需求的时候是显得非常的痛苦. 如果程度稍微好一些的朋友把label和textField的坐标和宽高设置成宏定义, 的确在改需求的时候方便了一些, 但是想来这个宏定义是数目是不少的. 过一段时间再回看这些宏会需要花上不少时间.
用一个简介的封装方法, 可以改善这种情况.
label和textField是成对存在的. 那么是不是可以把一对label和textField当作一个整体呢?
我们只需要计算相邻整体之间的间距即可, 内部的label和textField只需要在内部计算一次即可.
通常选择UIView来作为载体.
像这样把label 和TextField放到同个View上. 只要创建一个View 就同时显示出了一对Label 和TextField
这样就把上面的界面简化成了
一个界面的铺设就从一开始的8个控件, 简化到了4个空控件.
如何创建LTView (Label, TextField视图)
MRC模式下:
在.h文件中声明需要和外界交互的接口属性:
@property (nonatomic, retain) UILabel *label;
@property (nonatomic, retain) UITextField *textField;
在.m中重写初始化方法:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
self.label = [[UILabel alloc] initWithFrame:CGRectMake(width / 5, 0, width / 5, height)];
[self addSubview:_label];
[_label release];
self.textField = [[UITextField alloc] initWithFrame:CGRectMake(width - (width / 5) - width / 3, 0, width / 3, height)]; // width / 5 为右侧的留白空间.
self.textField.borderStyle = UITextBorderStyleRoundedRect;
self.textField.clearButtonMode = UITextFieldViewModeAlways;
self.textField.returnKeyType = UIReturnKeyDone;
self.textField.delegate = self;
[self addSubview:_textField];
[_textField release];
}
return self;
}
// 在此省略响应return键回收键盘代码.
这样就把一对label和textField封装成了一个View, 只要创建一个LTView 就等于创建了一对label和textField.
在外界可以改变LTView的大小和坐标, 但是没法改变label和textField的比例大小和间距等属性.
但是这样的好处是, 在外界不需要考虑label和textField的坐标和大小了.
只需要计算好LTView的铺设即可.
MVC
MVC是一种设计模式
好处: 让视图View可以复用
应用:
- 视图层(View) 只写视图的布局
- 数据模型层(Model) 只写数据的结构(也就是NS开头的)
- 控制器(Controller) 负责逻辑部分 (例如从model里取数据, 然后更改视图显示)
MVC 把工程中的代码 模块化(降低耦合性), 尽量让视图部分的代码 可以复用
在UI中 一般一个视图控制器(Controller)控制一个页面.
- M: model // 数据层
- V: view // 界面层
- C: control // 控制层
具体的作用是将数据, 数据处理, 显示分离, 降低之间的耦合度, 使得V层的可复用性更高.
比如: 若是将M层和V层混合, 不通过C. 那么极有可能导致View放到另一个程序里无法实现.
通常而言, V和C是一一对应的. 有多少个V就有多少个C.
但是有多少个M就不一定了, 要根据需求.
视图控制器
视图控制器UIViewController属于MVC中C(control)层.
作用是从M层拿数据, 对数据进行处理等, 将修改后的数据返回给V层显示
视图控制器的生命周期.
每个视图控制器中都自带一个属性view. 在AppDelegate类中, 将self.window的根视图控制器设置为想要第一个显示的视图控制器对应的view.
利用视图控制器管理view, 通常一个界面对应一个controller. 一个界面可能有多个自定义视图. 这样可以将界面模块化, 在查询bug的时候可以快速定位到bug出现在哪个controller的哪个自定义界面里面.
tips:
// FUNCTION 打印调用了哪个方法
// LINE 打印这个方法在多少行
在某个方法里可以查看当前方法在什么时候在哪一行调用
NSLog(@"%s %d", __FUNCTION__, __LINE__);
屏幕旋转
通常获取并处理屏幕旋转事件分为以下5个步骤
- 允许屏幕旋转
- 指定屏幕旋转的方向
- 找到旋转触发的方法
- 判断屏幕方向 更改布局
- 测试一下
第一步
// 第一步:允许屏幕旋转方法:
- (BOOL)shouldAutorotate {
return YES;
}
第二步
// 2. 指定屏幕旋转的方向
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAll; // 全方向支持
}
// UIInterfaceOrientationMaskPortrait 指定屏幕只能竖屏显示
// UIInterfaceOrientationMaskLandscapeLeft 指定屏幕只能左转显示
// UIInterfaceOrientationMaskLandscapeRight 指定屏幕只能右转显示
// UIInterfaceOrientationMaskPortraitUpsideDown 指定屏幕只能倒着显示
// UIInterfaceOrientationMaskLandscape 指定屏幕只能坐转或者右转显示
// UIInterfaceOrientationMaskAll 屏幕可以任意转向
// UIInterfaceOrientationMaskAllButUpsideDown 指定屏幕不能倒着显示
第三步
// 3. 当屏幕旋转的时候触发本事件.
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
NSLog(@"%@", NSStringFromCGSize(size)); // 打印当前屏幕的尺寸
}
// 以iphone6的屏幕为例
// 当屏幕竖屏的时候, 屏幕大小为(375,667)
// 当屏幕横屏的时候, 屏幕大小为(667,375)
// 注意, [UIScreen mainScreen].bounds.size 的值会发生变化.宽高的值会交换.
第四步
// 4. 判断屏幕方向 更改布局
// 当屏幕的尺寸发生变化的时候, 会触发- (void)layoutSubviews;方法
// 可以在controller或者自定义视图中重写layoutSubviews方法更改视图的尺寸
- (void)layoutSubviews {
// frame发生变化 相当于 横屏了, 这时候需要重新布局视图.
[super layoutSubviews];
// 先执行一下父类的layoutSubviews, 防止出现纰漏
// 取出此应用程序 一般单例方法的命名规范 就是以shared开头
UIApplication *app = [UIApplication sharedApplication];
// 获取app的方向
if (app.statusBarOrientation == UIInterfaceOrientationPortrait || app.statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown) {
// 屏幕正放, 倒放都是竖屏状态
NSLog(@"竖着"); // 此时的尺寸是(375,667).
// 以(375,667)的尺寸对视图进行布局.
self.userNameLTView.frame = CGRectMake((375 - kLTViewWidth) / 2, 667 / 5, kLTViewWidth, KLTViewHeight);
self.passwordLTView.frame = CGRectMake((375 - kLTViewWidth) / 2, 667 / 5 + KLTViewHeight + KLTViewRowDistance, kLTViewWidth, KLTViewHeight);
self.buttonView.frame = CGRectMake(75, self.passwordLTView.frame.origin.y + self.passwordLTView.frame.size.height + 30, 225, 30);
} else { // 不是竖放状态就是横放状态
// 此时屏幕的尺寸是(667, 375)
// 已横放状态下的尺寸对界面进行布局.
self.userNameLTView.frame = CGRectMake((375 - kLTViewWidth) / 2, 667 / 5, kLTViewWidth - 20, KLTViewHeight);
self.passwordLTView.frame = CGRectMake((375 - kLTViewWidth) / 2 + kLTViewWidth + 10, 667 / 5, kLTViewWidth - 20, KLTViewHeight);
self.buttonView.frame = CGRectMake(152, self.passwordLTView.frame.origin.y + KLTViewHeight + 40, 363, KLTViewHeight);
NSLog(@"横着");
}
}
注意, 是先触发了viewWillTransitionToSize:方法, 修改了屏幕的size. 使得视图的frame发生了变化, 再触发了layoutSubviews方法, 修改视图的尺寸.
第五步
运行模拟机, 测试一下程序是否正确被修改了.
花絮:
视图控制器可以作为一个容器, 将另一个视图控制器作为本视图控制器的子控制器.
[self addChildViewController:childVC];
注意此时要再将子视图控制器的view添加到根视图控制器的view上, 作为根控制器的view的子视图
[self.view addSubview:secondV.view];
本人猜测, 本方法其实只是为了解耦. 在用途上来说作用并不大.