自定義控件

自定義控件

本節知識點:

  1. 封裝子控件的創建
  2. 自定義控件的步驟
  3. 提供設置子控件數據接口
  4. 分析封裝的好處
  5. 簡單的MVC思想

1. 封裝子控件的創建

  • 需要自定義控件的原因

    • 控制器管的太多,耦合性太強,擴展性差
    • 商品界面是獨立的一塊,可能會用到其他的界面
    • 產品的需求經常改,就要求代碼的擴展性要好
  • 目的:封裝控件內部的細節

  • 步驟:
    • 繼承自系統自帶的控件,寫一個屬於自己的控件
    • 創建自定義控件(如:CDHShopView)
    • 重寫構造方法添加子控件(init…)
    • layoutSubviews設置子控件的frame
    • 提供設置子控件數據接口
    • 提供方便創建自定義控件的方法(構造方法 和 類工廠方法)

2. 自定義控件的步驟

  • 新建一個繼承UIView的類(如:CDHShopView)

  • 重寫initWithFrame:方法,並在該方法中添加子控件

    • 創建控件時如果使用 init 方法,則會在內部調用initWithFrame: 這個方法(因此一般直接重寫寫此方法)
    • 在添加子控件時,不能設置子控件的frame(位置、尺寸),因爲當前控件在創建時,可能沒有設置 frame;
    • 可以設置其他屬性,比如背景顏色、對其方式,等;
    • 注意:一定要將局部創建出來的控件加入父控件中,並且一定要給父控件中對應的子控件獲取到該局部控件;
    - (instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        // 添加圖片控件UIImageView
        UIImageView *iconImageView = [[UIImageView alloc] init];
        [self addSubview:iconImageView];
        self.iconImageView = iconImageView;
        // 注意:一定要將這個局部定義出來的控件加入父控件中,並且一定要給父控件中對應的子控件獲取到該局部控件;
        // 原因1:ARC 中默認所有指針變量都是強指針,因此局部控件被定義出來是被強指針指向,
        // 原因2:將局部控件添加入父控件之後,就表示父控件(內部數組中元素,)也指向了該局部變量,
        //       只要有強指針指向則該變量就不會被釋放,因此只要父控件一直存在着父控件中對應的這個局部控件也就存在,
        //       這樣也就保證的局部定義出來的控件的存儲空間暫時不會被釋放;
        // 原因3:要將局部變量控件賦值給父控件對應的子控件,雖然父控件中對應的子控件也是弱指針(weak)指向,
        //       但該局部控件指向是強指針指向,並且只有父控件中對應的子控件獲取到了該控件,
        //       才能通過 父控件.子控件 來設置子控件的位置、尺寸、背景色,等一系列的操作(實際也就是設置局部控件的屬性);
    
        // 添加文字文字控件UILabel
        UILabel *nameLabel = [[UILabel alloc] init];
        nameLabel.textAlignment = NSTextAlignmentCenter;
        [self addSubview:nameLabel];
        self.nameLabel = nameLabel;
    }
    return self;
    }
  • 重寫layoutSubviews方法,設置子控件的frame

    • 該方法是繼承 View 的方法
    • 佈局子控件,設置子控件的位置和尺寸
    • 當前控件的尺寸發生改變會調用該方法
    • 第一次顯示的時候會調用該方法
    • 注意:重寫一定要先調用[super layoutSubviews];先佈局父控件;
    - (void)layoutSubviews
    {
      // 這裏一定要寫,並且要寫在最前面
      [super layoutSubviews];
    
      CGFloat shopW = self.frame.size.width;
      CGFloat shopH = self.frame.size.height;
      NSLog(@"layoutSubviews");
    
      self.iconImageView.frame = CGRectMake(0, 0, shopW, shopW);
      self.nameLabel.frame = CGRectMake(0, shopW, shopW, shopH - shopW);
    }
  • 提供設置子控件數據接口(三種方法)

    • 方法一:直接暴漏子控件方便外面設置數據(子控件聲明在 .h 文件)
    • 方法二:提供子控件外部接口方便設置數據(重寫子控件setter 方法)
    • 方法三:提供模型屬性及其接口(重寫模型屬性setter 方法)
  • 提供方便快捷創建自定義控件的方法

    • 自定義構造方法
    - (instancetype)initWithShop:(CDHShop *)shop
    {
      // [super init] 內部會調用當前類的initWithFrame:
      if (self = [super init]) {
          self.shop =  shop;
      }
      return self;
    }
    • 類工廠方法
    + (instancetype)shopViewWithShop:(CDHShop *)shop{
    // 注意:這裏最好使用 self 而不是對應該 類名(CDHShopView) ,有利於其他自定義控件繼承該控件的擴展
    CDHShopView * shopView = [[self alloc]initWithShop:shop];
    return shopView;
    }

3. 提供設置子控件數據接口

  • 3種設計思路

  • 方法一:直接暴漏子控件方便外面設置數據

    • 自定義控件的成員變量聲明在 .h 文件中
    • 在控制器定義創建的自定義控件的 .m 文件中包含自定義控件的 .h 文件
    • 直接在控制器 的 .m 文件中設置自定義控件的數據

    • 特點:

    • 控制器管的太多,耦合性太強,擴展性差;
    • 自定義控件直接暴露內部控件,數據不安全;

    • 例子:

    //  自定義控件CDHShopView.h 文件
    
    
    #import <UIKit/UIKit.h>
    
    
    @interface CDHShopView : UIView
    
    // 方法一:直接暴漏子控件方便外面設置數據
    @property (weak, nonatomic) IBOutlet UIImageView *iconImageView;
    @property (weak, nonatomic) IBOutlet UILabel *nameLabel;
    
    @end
    //  控制器 ViewController.m 文件
    
    /******** 創建商品父控件  ********/
    // 創建商品父控件(自定義view)
    CDHShopView *shopView = [[CDHShopView alloc] init];
    shopView.frame = CGRectMake(shopX, shopY, shopW, shopH);
    // 將商品父控件添加到shopsView
    [self.shopsView addSubview:shopView];
    //  控制器 ViewController.m 文件
    
    /******** 設置數據 方法一 ********/
    CDHShop *shop = self.shops[index];
    // 1.第一種設計思路:直接給自定義控件內部對應子控件設置數據
    shopView.iconImageView.image = [UIImage imageNamed:shop.icon];
    shopView.nameLabel.text = shop.name;
  • 方法二:提供每個子控件外部接口方便設置數據

    • 自定義控件的成員變量聲明在 .m 文件寫在類擴展中(保證私有性、安全性高)
    • 在控制器定義創建的自定義控件的 .m 文件中包含自定義控件的 .h 文件
    • 重寫每個控件的setter 方法,提供每個子控件的數據接口給外部設置數據

    • 特點:

    • 控制器管的太多,耦合性太強,擴展性較差;
    • 內部數據不暴露,但設置數據過程依然繁瑣;

    • 例子:

    // 在自定義控件 CDHShopView.h 文件,聲明每個子控件的外部接口
    - (void)setIcon:(NSString *)icon;
    - (void)setName:(NSString *)name;
    - (void)setIcon:(NSString *)icon name:(NSString *)name;
    // 在自定義控件 CDHShopView.m 文件,實現每個子控件的外部接口
    - (void)setIcon:(NSString *)icon{
      self.iconImageView.image = [UIImage imageNamed:icon];
    }
    - (void)setName:(NSString *)name{
      self.nameLabel.text = name;
    }
    - (void)setIcon:(NSString *)icon name:(NSString *)name{
       self.iconImageView.image = [UIImage imageNamed:icon];
       self.nameLabel.text = name;
    }
    //  控制器 ViewController.m 文件
    /******** 設置數據 方法二 ********/
    CDHShop *shop = self.shops[index];
    // 2.第二種設計思路:通過自定義控件內部對應子控件的(setter方法)接口設置數據
    [shopView setIcon:shop.icon];
    [shopView setName:shop.name];
    //    [shopView setIcon:shop.icon name:shop.name];
  • 方法三:提供模型屬性及其接口 (常用)

    • 自定義控件的成員變量聲明在 .m 文件寫在類擴展中(保證私有性、安全性高)
    • 在控制器定義創建的自定義控件的 .m 文件中包含自定義控件的 .h 文件
    • 重寫模型 setter 方法,提供模型數據接口給外部設置數據
    • 特點:
    • 擴展性好;
    • 內部數據不暴露,但設置數據過程較爲方便;

    • 例子:

    // 在自定義控件 CDHShopView.h 文件,提供模型屬性
    @property (nonatomic ,strong)XMGShop *shop;
    // // 在自定義控件 CDHShopView.m 文件,實現模型屬性接口設置數據
    - (void)setShop:(CDHShop *)shop{
      // 注意: 第一件事就是先給模型屬性賦值,方便其他地方使用該模型屬性
      _shop = shop;
      self.iconImageView.image = [UIImage imageNamed:shop.icon];
      self.nameLabel.text = shop.name;
    }
    // 3.第三種設計思路:通過模型的(setter方法)接口設置數據
    shopView.shop = shop;

4. 封裝的好處與基本步驟

  1. 如果一個view內部的子控件比較多,一般會考慮自定義一個view,把它內部子控件的創建屏蔽起來,不讓外界關心
  2. 外界可以傳入對應的模型數據給view,view拿到模型數據後給內部的子控件設置對應的數據
  • 封裝的優點:方便在其他項目中使用,擴展性強

5. 簡單的MVC思想

  • 簡單的MVC
  • M: Model 數據模型,數據
  • V: View 視圖,顯示數據
  • C: Controller 控制,大管家

    • MVC:是一種設計模式(開發總結出來的經驗、套路)

發佈了34 篇原創文章 · 獲贊 12 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章