iOS中标准的自定义控件(UIView的封装)

iOS中标准的自定义控件(UIView的封装)

前言,在开发过程中,由于系统的控件不能达到开发者的需求,导致自定义控件使用的频率非常高,基本上项目中处处都是自定义的控件。本文将介绍自定义控件的总体使用(也就是UIView的封装),不细分单独控件的自定义(如UIButtonUITabBar的自定义)其实单独控件的自定义与本文也是大同小异。另外本文下面的例子用自定义UIButton更为合适,但是本人出于对所有自定义控件的一个总结,用了自定义UIView。

UIView的封装

  • 如果一个view内部的子控件比较多,一般会考虑自定义一个view,把它内部子控件的创建屏蔽起来,不让外界关心

  • 外界可以传入对应的模型数据给view,view拿到模型数据后给内部的子控件设置对应的数据

  • 继承自系统自带的控件,写一个属于自己的控件

  • 目的:封装空间内部的细节,不让外界关心

  • 类似于下面这张图,如果把下面所有控件加入到控制器的View中,控制器的View将拥有太多子控件,而且非常不好管理每个控件的位置,这个时候把他们封装起来,外界不用关心其内部结构,那将方便太多。

iOS中标准的自定义控件(UIView的封装)_第1张图片

UIView的封装有两种方式

通过纯代码封装

  • 步骤:

    1. 新建一个继承UIView的类
    2. 在刚刚新建类的类扩展中添加子控件属性(用weak声明,防止内存泄露)
    3. initWithFrame:方法中添加子控件
    4. layoutSubviews方法中设置子控件的frame(在该方法中一定要调用[super layoutSubviews]方法)
    5. 提供一个模型属性,重写模型属性的set方法
    6. 在该setter方法中取出模型属性,给对应的子控件赋值
  • UIView的封装代码如下:


    /** * CustomView.h文件 */

    // 步骤1 新建一个继承UIView的类
    #import <UIKit/UIKit.h>
    @class CustomModel;
    @interface CustomView : UIView
    // 在这里为了方便,可以自行添加构造方法,方便使用
    // 步骤5 提供一个`模型`属性,重写模型属性的set方法
    @property (nonatomic, strong) CustomModel *model;
    @end

    /** * CustomView.m文件 */
    #import "CustomView.h"
    #import "CustomModel.h"
    @interface CustomView ()
    // 步骤2 在刚刚新建类的`类扩展`中添加子控件属性(用`weak`声明,防止内存泄露)
    @property (nonatomic, weak) UIImageView *iconImageView;

    @property (nonatomic, weak) UILabel *nameLabel;

    @end

    @implementation CustomView

    // 步骤3 在initWithFrame:方法中添加子控件
    - (instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            // 注意:该处不要给子控件设置frame与数据,可以在这里初始化子控件的属性
            UIImageView *iconImageView = [[UIImageView alloc] init];
            self.iconImageView = iconImageView;
            [self addSubview:iconImageView];

            UILabel *nameLabel = [[UILabel alloc] init];
            // 设置子控件的属性
            nameLabel.textAlignment = NSTextAlignmentCenter;
            nameLabel.font = [UIFont systemFontOfSize:10];
            self.nameLabel = nameLabel;
            [self addSubview:nameLabel];
        }
        return self;
    }

    // 步骤4 在`layoutSubviews`方法中设置子控件的`frame`(在该方法中一定要调用`[super layoutSubviews]`方法)
    - (void)layoutSubviews
    {
        [super layoutSubviews];

        CGFloat iconImageViewX = 0;
        CGFloat iconImageViewY = 0;
        CGFloat iconImageViewW = self.bounds.size.width;
        CGFloat iconImageViewH = 80;
        self.iconImageView.frame = CGRectMake(iconImageViewX, iconImageViewY, iconImageViewW, iconImageViewH);

        CGFloat nameLabelX = 0;
        CGFloat nameLabelY = iconImageViewH;
        CGFloat nameLabelW = iconImageViewW;
        CGFloat nameLabelH = self.bounds.size.height - iconImageViewH;
        self.iconImageView.frame = CGRectMake(nameLabelX, nameLabelY, nameLabelW, nameLabelH);
    }

    // 步骤6 在该`setter`方法中取出模型属性,给对应的子控件赋值
    - (void)setModel:(CustomModel *)model
    {
        _model = model;

        self.iconImageView.image = [UIImage imageNamed:model.icon];
        self.nameLabel.text = model.name;

    }


    @end

  • layoutSubviews在以下情况下会被调用:

    1. init初始化不会触发layoutSubviews
    2. addSubview会触发layoutSubviews
    3. 设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
    4. 滚动一个UIScrollView会触发layoutSubviews
    5. 旋转Screen会触发父UIView上的layoutSubviews事件
    6. 改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件
  • 模型代码如下:


    /** * CustomModel.h文件 */
    #import <Foundation/Foundation.h>

    @interface CustomModel : NSObject
    /** * 名字 */
    @property (nonatomic, copy) NSString *name;
    /** * 图片 */
    @property (nonatomic, copy) NSString *icon;

    + (instancetype)modelWithName:(NSString *)name icon:(NSString *)icon;
    - (instancetype)initWithName:(NSString *)name icon:(NSString *)icon;

    @end

    /** * CustomModel.m文件 */
    #import "CustomModel.h"

    @implementation CustomModel
    + (instancetype)modelWithName:(NSString *)name icon:(NSString *)icon
    {
        return [[self alloc] initWithName:name icon:icon];
    }

    - (instancetype)initWithName:(NSString *)name icon:(NSString *)icon
    {
        if (self = [super init]) {
            self.name = name;
            self.icon = icon;
        }
        return self;
    }
    @end
  • 如何使用?只需在控制器代码中执行4个步骤,简单方便:

    // 创建自定义的View
    CustomView *customView = [[CustomView alloc] init];
    // 设置数据
    CustomModel *model = [CustomModel modelWithName:@"hosea_zhou" icon:@"1"];
    customView.model = model;
    // 设置frame
    customView.frame = CGRectMake(100, 100, 67, 100);
    // 添加子控件
    [self.view addSubview:customView];

  • 运行结果
    iOS中标准的自定义控件(UIView的封装)_第2张图片

通过Xib+代码(简单方便)

  • 步骤:
    1. 新建一个继承UIView的类
    2. 新建一个xib文件(xib的文件名最好和控件名一样)
      操作:New File->iOS->User Interface->Empty

    3. 添加子控件、设置子控件属性
    4. 修改最外面那个控件的class为控件类名
      iOS中标准的自定义控件(UIView的封装)_第3张图片
    5. 将子控件进行连线(按住control键拖线)
      iOS中标准的自定义控件(UIView的封装)_第4张图片
    6. 封装xib的加载过程
      iOS中标准的自定义控件(UIView的封装)_第5张图片
    7. 提供一个模型属性,重写模型属性的set方法
    8. 在该setter方法中取出模型属性,给对应的子控件赋值 步骤6和步骤7与纯代码封装步骤5和步骤6相同。

注意点

  • 一个控件有2种创建方式

    • 通过代码创建
      初始化时一定会调用initWithFrame:方法

    • 通过xib\storyboard创建
      初始化时不会调用initWithFrame:方法,只会调用initWithCoder:方法,初始化完毕后会调用awakeFromNib方法注意要在在awakeFromNib中初始化子控件

  • 有时候希望在控件初始化时做一些初始化操作,比如添加子控件、设置基本属性
    这时需要根据控件的创建方式,来选择在initWithFrame:、initWithCoder:、awakeFromNib的哪个方法中操作

总结

  • 两种方法封装UIView的比较
    • 在调整子控件的frame时,使用纯代码比xib更灵活,子控件可以在layoutSubviews方法中灵活调整自己的frame。而用xib相对于比较死板,但是更简单,更方便。
    • 建议:自定义UIView时,如果该View一直一个样式,推荐使用xib,简单方便,而子控件经常随着父控件变化而变化,推荐使用纯代码,灵活多变。

本文如有不当之处或者有误欢迎指出,谢谢~

你可能感兴趣的:(ios,UIView,ios开发,控件,开发人员)