iOS中標準的自定義控件(UIView的封裝)
前言,在開發過程中,由於系統的控件不能達到開發者的需求,導致自定義控件使用的頻率非常高,基本上項目中處處都是自定義的控件。本文將介紹自定義控件的總體使用(也就是UIView的封裝),不細分單獨控件的自定義(如UIButton
、UITabBar
的自定義)其實單獨控件的自定義與本文也是大同小異。另外本文下面的例子用自定義UIButton更爲合適,但是本人出於對所有自定義控件的一個總結,用了自定義UIView。
UIView的封裝
如果一個
view內部
的子控件比較多,一般會考慮自定義一個view,把它內部子控件的創建屏蔽起來,不讓外界關心外界可以傳入
對應的模型數據
給view,view拿到模型數據後給內部的子控件設置對應的數據繼承自系統自帶的控件,寫一個屬於自己的控件
目的:封裝空間內部的細節,不讓外界關心
類似於下面這張圖,如果把下面所有控件加入到控制器的View中,控制器的View將擁有太多子控件,而且非常不好管理每個控件的位置,這個時候把他們封裝起來,外界不用關心其內部結構,那將方便太多。
UIView的封裝有兩種方式
通過純代碼封裝
步驟:
- 新建一個繼承
UIView
的類 - 在剛剛新建類的
類擴展
中添加子控件屬性(用weak
聲明,防止內存泄露) - 在
initWithFrame:
方法中添加子控件 - 在
layoutSubviews
方法中設置子控件的frame
(在該方法中一定要調用[super layoutSubviews]
方法) - 提供一個
模型
屬性,重寫模型屬性的set方法 - 在該
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在以下情況下會被調用:
- init初始化
不會
觸發layoutSubviews - addSubview會觸發layoutSubviews
- 設置view的Frame會觸發layoutSubviews,當然前提是frame的值設置前後發生了變化
- 滾動一個UIScrollView會觸發layoutSubviews
- 旋轉Screen會觸發父UIView上的layoutSubviews事件
- 改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件
- init初始化
模型代碼如下:
/**
* 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+代碼(簡單方便)
- 步驟:
- 新建一個繼承
UIView
的類
- 新建一個xib文件(xib的文件名最好和控件名一樣)
操作:New File->iOS->User Interface->Empty
- 添加子控件、設置子控件屬性
- 修改最外面那個控件的class爲控件類名
- 將子控件進行連線(按住control鍵拖線)
- 封裝xib的加載過程
- 提供一個
模型
屬性,重寫模型屬性的set方法 - 在該
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
,簡單方便,而子控件經常隨着父控件變化而變化,推薦使用純代碼
,靈活多變。
- 在調整子控件的