一、需求实现一个前后带图标的输入框
这是一个简单的自定义控件,很容易想到自定义一个视图(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