iOS中標準的自定義控件(UIView的封裝)

iOS中標準的自定義控件(UIView的封裝)

前言,在開發過程中,由於系統的控件不能達到開發者的需求,導致自定義控件使用的頻率非常高,基本上項目中處處都是自定義的控件。本文將介紹自定義控件的總體使用(也就是UIView的封裝),不細分單獨控件的自定義(如UIButtonUITabBar的自定義)其實單獨控件的自定義與本文也是大同小異。另外本文下面的例子用自定義UIButton更爲合適,但是本人出於對所有自定義控件的一個總結,用了自定義UIView。


UIView的封裝

  • 如果一個view內部的子控件比較多,一般會考慮自定義一個view,把它內部子控件的創建屏蔽起來,不讓外界關心

  • 外界可以傳入對應的模型數據給view,view拿到模型數據後給內部的子控件設置對應的數據

  • 繼承自系統自帶的控件,寫一個屬於自己的控件

  • 目的:封裝空間內部的細節,不讓外界關心

  • 類似於下面這張圖,如果把下面所有控件加入到控制器的View中,控制器的View將擁有太多子控件,而且非常不好管理每個控件的位置,這個時候把他們封裝起來,外界不用關心其內部結構,那將方便太多。

封裝View舉例

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];

  • 運行結果
    運行結果

通過Xib+代碼(簡單方便)

  • 步驟:
    1. 新建一個繼承UIView的類
      新建類
    2. 新建一個xib文件(xib的文件名最好和控件名一樣)
      操作:New File->iOS->User Interface->Empty
      新建xib
      新建xib
    3. 添加子控件、設置子控件屬性
    4. 修改最外面那個控件的class爲控件類名
      修改xib中view的類名
    5. 將子控件進行連線(按住control鍵拖線)
      子控件拖線
    6. 封裝xib的加載過程
      封裝xib的加載過程
    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,簡單方便,而子控件經常隨着父控件變化而變化,推薦使用純代碼,靈活多變。

本文如有不當之處或者有誤歡迎指出,謝謝~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章