自定義控件
本節知識點:
- 封裝子控件的創建
- 自定義控件的步驟
- 提供設置子控件數據接口
- 分析封裝的好處
- 簡單的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. 封裝的好處與基本步驟
- 如果一個view內部的子控件比較多,一般會考慮自定義一個view,把它內部子控件的創建屏蔽起來,不讓外界關心
- 外界可以傳入對應的模型數據給view,view拿到模型數據後給內部的子控件設置對應的數據
- 封裝的優點:方便在其他項目中使用,擴展性強
5. 簡單的MVC思想
- 簡單的MVC
- M: Model 數據模型,數據
- V: View 視圖,顯示數據
C: Controller 控制,大管家
- MVC:是一種設計模式(開發總結出來的經驗、套路)