iOS - 建立可以在 InterfaceBuilder 中實時預覽的自定義控制元件
一、需求實現一個前後帶圖示的輸入框
這是一個簡單的自定義控制元件,很容易想到自定義一個檢視(UIView),然後前後的圖示使用 UIImageView 或者 UIButton 顯示,中間放一個 UITextField 就可以了
實現方式上可以 InterfaceBuilder 建立,也可以使用純程式碼實現,這個可以根據自己喜好及專案確定
二、實現 InterfaceBuilder 中實時預覽
要實現上面的一個需求,相信對大多數人來說並不難,本文主要講解怎麼讓自定義控制元件支援純程式碼建立,並且也支援在 InterfaceBuilder 中實時預覽,以及本人在編寫過程中遇到問題
想要讓自定義的控制元件支援在 InterfaceBuilder 中實時預覽,其實也並不難,需要用到兩個關鍵字 IB_DESIGNABLE 和 IBInspectable
IB_DESIGNABLE:用來指示該類可以在 InterfaceBuilder ,在 InterfaceBuilder 中,新增一個 UIView 後,指定 Custom Class 中的 Class 為該類就可以了;這個關鍵字可以下載 .h 或者 .m 中 @interface 前面即可
程式碼如下:
IB_DESIGNABLE @interface ImageTextField : UIView
IBInspectable:用來指示屬性在 InterfaceBuilder 中顯示屬性,可以實時設定;這個關鍵字只用新增到屬性型別前面即可
程式碼如下:
@property (nonatomic, strong) IBInspectable NSString *text;
這樣準備工作,接下來就是在具體實現過程
三、實現及注意事項
1. 純程式碼初始化
對於純程式碼建立,我們要有一個初始化方法,init 或者 initWithFrame 或者自定義的初始化方法,我們只需要把建立新增子控制元件在初始化方法中一步一步新增即可,程式碼約束可以新增到 layoutSubviews
#pragma mark --- 支援程式碼建立 - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setViewsWithFrame:frame]; } return self; }
- (void)layoutSubviews { [super layoutSubviews]; // 約束在這裡新增,才能保證約束正確執行 [self setConstraints]; }
必須要重寫初始化方法:- (instancetype)initWithFrame:(CGRect)frame 我們在使用 InterfaceBuilder 的時候,會呼叫該方法,所以修改該方法呼叫的程式碼都會實時顯示到 InterfaceBuilder 中,如果是我們自定義的初始化方法,則不會在修改程式碼時實時顯示,我們為了能夠實時預覽到修改效果,需要重寫系統的初始化方法,同時為了支援純程式碼建立例項,
可以把初始化要實現的程式碼封裝起來,這樣的話可以方便的在自定義以及重寫的初始化方法中方便呼叫。
那麼怎麼才能讓我們的修改實時顯示在,我們需要實現另外一個方法:prepareForInterfaceBuilder 從方法名稱就可以猜測到,寫在該方法中的程式碼會反映的到 InterfaceBuilder 這樣就能看到實時修改後的效果
示例程式碼:
#pragma mark --- 實時更新到 InterfaceBuilder - (void)prepareForInterfaceBuilder {
[self setConstraints]; }
為什麼在這裡呼叫?如果在 init 中呼叫,會導致在 InterfaceBuilder 設定的屬性不起作用,那麼就看不到真實的效果。這裡的程式碼只是在設計的時候會呼叫,真實的設定約束的程式碼是在 layoutSubviews 中呼叫
2. 不使用純程式碼建立
我們只需要在 InterfaceBuilder 中新增一個 UIView 中,然後設定 Class 為自定義的控制元件就可以了,但是視覺化新增的控制元件,怎麼初始化呢?就需要重寫下面這個方法來保證正確的執行,在程式執行時,系統會自動呼叫這個方法建立例項
#pragma mark --- 支援XIB建立 - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { // 這裡 Frame 可以任意指定,約束會調整檢視大小 [self setViewsWithFrame:CGRectZero]; } return self; }
完整程式碼如下
ImageTextField.h:
// // ImageTextField.h // EXOTerra // // 帶有圖片提示的輸入框 // // Created by huang zhengguo on 2020/1/2. // Copyright © 2020 Inledco. All rights reserved. // #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN IB_DESIGNABLE @interface ImageTextField : UIView // 文字 @property (nonatomic, strong) IBInspectable NSString *text; // 前面影象 @property (nonatomic, strong) IBInspectable UIImage *image; // 字型顏色 @property (nonatomic, strong) IBInspectable UIColor *textColor; // 佔位符 @property (nonatomic, strong) IBInspectable NSString *placeholder; // 佔位符顏色 @property (nonatomic, strong) IBInspectable UIColor *placeholderColor; // 是否隱藏輸入 @property (nonatomic, assign) IBInspectable BOOL secureTextEntry; // 小圖示 @property (nonatomic, strong) IBInspectable UIImage *iconImage; // 是否顯示小圖示 @property (nonatomic, assign) IBInspectable BOOL isIconVisible; // 小圖示點選回撥 @property (nonatomic, copy) void (^iconButtonClickCallback) (ImageTextField *, UIButton *, BOOL); @end NS_ASSUME_NONNULL_END
ImageTextField.m
// // ImageTextField.m // EXOTerra // // Created by huang zhengguo on 2020/1/2. // Copyright © 2020 Inledco. All rights reserved. // #import "ImageTextField.h" @interface ImageTextField() // 前面圖示 @property (nonatomic, strong) UIImageView *headerImageView; // 輸入框 @property (nonatomic, strong) UITextField *textField; // 後面圖示 @property (nonatomic, strong) UIButton *iconButton; @end @implementation ImageTextField #pragma mark --- 支援程式碼建立 - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setViewsWithFrame:frame]; } return self; } #pragma mark --- 支援XIB建立 - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { // 這裡 Frame 可以任意指定,在 InterfaceBuilder 中會重新設定 [self setViewsWithFrame:CGRectZero]; } return self; }#pragma mark --- 新增子檢視等 - (void)setViewsWithFrame:(CGRect)frame { self.layer.borderWidth = 1.0; self.layer.borderColor = [UIColor whiteColor].CGColor; self.layer.cornerRadius = 3.0; // 前端圖片 self.headerImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; self.headerImageView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:self.headerImageView]; // 輸入框 self.textField = [[UITextField alloc] init]; self.textField.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:self.textField]; // 尾部小按鈕圖示 self.iconButton = [[UIButton alloc] initWithFrame:CGRectZero]; self.iconButton.translatesAutoresizingMaskIntoConstraints = NO; [self.iconButton addTarget:self action:@selector(iconButtonClickAction:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:self.iconButton]; // 預設設定 self.placeholderColor = [UIColor lightGrayColor]; } #pragma mark --- 小圖示點選方法 - (void)iconButtonClickAction:(UIButton *)sender { if (self.iconButtonClickCallback) { self.iconButtonClickCallback(self, sender, self.secureTextEntry); } } - (void)setImage:(UIImage *)image { _image = image; _headerImageView.image = _image; } - (NSString *)text { return _textField.text; } - (void)setText:(NSString *)text { _textField.text = text; } - (void)setTextColor:(UIColor *)textColor { _textColor = textColor; _textField.textColor = _textColor; } - (void)setPlaceholder:(NSString *)placeholder { _placeholder = placeholder; _textField.placeholder = _placeholder; } - (void)setPlaceholderColor:(UIColor *)placeholderColor { if (_placeholder == nil || _placeholder.length == 0) { return; } _placeholderColor = placeholderColor; NSMutableAttributedString *placeholderAttributedString = [[NSMutableAttributedString alloc] initWithString:_placeholder]; [placeholderAttributedString addAttribute:NSForegroundColorAttributeName value:_placeholderColor range:NSMakeRange(0, _placeholder.length)]; _textField.attributedPlaceholder = placeholderAttributedString; } - (void)setSecureTextEntry:(BOOL)secureTextEntry { _secureTextEntry = secureTextEntry; _textField.secureTextEntry = _secureTextEntry; } - (void)setIconImage:(UIImage *)iconImage { _iconImage = iconImage; if (_iconImage) { [_iconButton setImage:_iconImage forState:UIControlStateNormal]; } } - (void)setIsIconVisible:(BOOL)isIconVisible { _isIconVisible = isIconVisible; _iconButton.hidden = !_isIconVisible; } #pragma mark --- 實時更新到 InterfaceBuilder - (void)prepareForInterfaceBuilder { [self setConstraints]; } #pragma mark --- 設定約束 - (void)setConstraints { // 圖片約束 NSLayoutConstraint *headerImageLeading = [NSLayoutConstraint constraintWithItem:self.headerImageView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1.0 constant:8.0]; NSLayoutConstraint *headerImageTop = [NSLayoutConstraint constraintWithItem:self.headerImageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:8.0]; NSLayoutConstraint *headerImageBottom = [NSLayoutConstraint constraintWithItem:self.headerImageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-8.0]; NSLayoutConstraint *headerImageWidth = [NSLayoutConstraint constraintWithItem:self.headerImageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:-8.0]; [self addConstraints:@[headerImageLeading, headerImageTop, headerImageBottom, headerImageWidth]]; // 小圖示約束 NSLayoutConstraint *iconButtonTop = [NSLayoutConstraint constraintWithItem:self.iconButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:8.0]; NSLayoutConstraint *iconButtonBottom = [NSLayoutConstraint constraintWithItem:self.iconButton attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-8.0]; NSLayoutConstraint *iconButtonTrailing = [NSLayoutConstraint constraintWithItem:self.iconButton attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:-8.0]; NSLayoutConstraint *iconButtonWidth = [NSLayoutConstraint constraintWithItem:self.iconButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:-16.0]; if (self.isIconVisible == NO) { // 如果圖示不可見,設定寬度為0的約束 iconButtonWidth = [NSLayoutConstraint constraintWithItem:self.iconButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:0.0]; } [self addConstraints:@[iconButtonWidth, iconButtonTop, iconButtonBottom, iconButtonTrailing]]; // 輸入框約束 NSLayoutConstraint *textFieldImageLeading = [NSLayoutConstraint constraintWithItem:self.textField attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.headerImageView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:8.0]; NSLayoutConstraint *textFieldImageTop = [NSLayoutConstraint constraintWithItem:self.textField attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]; NSLayoutConstraint *textFieldImageBottom = [NSLayoutConstraint constraintWithItem:self.textField attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]; NSLayoutConstraint *textFieldTrailing = [NSLayoutConstraint constraintWithItem:self.textField attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.iconButton attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0]; [self addConstraints:@[textFieldImageLeading, textFieldImageTop, textFieldImageBottom, textFieldTrailing]]; } - (void)layoutSubviews { [super layoutSubviews]; // 約束在這裡新增,才能保證使用使用程式碼建立時正常顯示 [self setConstraints]; } /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ @end