爬爬爬之路:UI(三)自定义视图 MVC浅谈 视图控制器 屏幕旋转事件获取

自定义视图

自定义视图是一种封装的方法

通常一些复杂的界面都是由一些常用的控件组合构成的, 将这些组合提取出来.写成一个组合控件, 在完成复杂界面的时候, 就可以通过组合控件简化界面的逻辑.这个组合控件就称为自定义视图.
自定义视图的优劣取决于它的复用性. 自定义视图的复用性越高, 则它的质量就越高. 一个高质量的自定义视图, 可以让程序员在铺设界面的时候极大的缩减代码量和时间.

如何完成一个自定义视图

这里举个简单的例子.
比如一个常见的注册界面.
除了button, 通常而言都是 一个label对应一个textField, 且两者是在同一行上的.

这时候如果对每个控件都算坐标和是不是非常的麻烦?
通常而言, label好写, 但是它对应的textField坐标计算起来就十分的复杂了. 需要考虑到label和textField横向的间距, 需要考虑到label和textField两侧对应的留白空隙大小的对称. 这非常复杂.
比如这样的一个界面:
常见的注册界面布局

通常连续3对或者以上的label和textField的坐标已经非常复杂了, 如果计算坐标还是用数字, 在改需求的时候是显得非常的痛苦. 如果程度稍微好一些的朋友把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可以复用

  • 应用:

    1. 视图层(View) 只写视图的布局
    2. 数据模型层(Model) 只写数据的结构(也就是NS开头的)
    3. 控制器(Controller) 负责逻辑部分 (例如从model里取数据, 然后更改视图显示)

MVC 把工程中的代码 模块化(降低耦合性), 尽量让视图部分的代码 可以复用
在UI中 一般一个视图控制器(Controller)控制一个页面.

  1. M: model // 数据层
  2. V: view // 界面层
  3. 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个步骤

  1. 允许屏幕旋转
  2. 指定屏幕旋转的方向
  3. 找到旋转触发的方法
  4. 判断屏幕方向 更改布局
  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];
本人猜测, 本方法其实只是为了解耦. 在用途上来说作用并不大.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值