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

原文地址:https://www.cnblogs.com/huangzhengguo/p/12179213.html

时间: 2024-10-07 23:02:49

iOS - 创建可以在 InterfaceBuilder 中实时预览的自定义控件的相关文章

Android中实时预览UI和编写UI的各种技巧

一.啰嗦 之前有读者反馈说,你搞这个所谓的最佳实践,每篇文章最后就给了一个库,感觉不是很高大上.其实,我在写这个系列之初就有想过这个问题.我的目的是:给出最实用的库来帮助我们开发,并且尽可能地说明这个库是如何编写的,希望让初创公司的程序员少写点给后人留坑的代码(想必大家对此深有体会).我之前给出的库都是很简单基础的,基本是一看就懂(但足够精妙),如果以后的文章涉及到了复杂的库,我会专门附加一篇库的讲解文.如果一个库的原理你知道,此外这个库很容易扩展和维护,而且它还用到了很多最佳实践的经验,你为什

UI实时预览最佳实践(转)

UI实时预览最佳实践 概要:Android中实时预览UI和编写UI的各种技巧.本文的例子都可以在结尾处的示例代码中看到并下载.如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以提交pull request.本文的代码主要是基于作者的实际经验编写的,如果你有其他的技巧和方法也可以参与进来一起完善此文. 文章固定连接:https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.9/ui/u

Notepad++中实现Markdown语法高亮与实时预览

Notepad ++是一个十分强大的编辑器,除了可以用来制作一般的纯文字说明文件,也十分适合编写计算机程序代码.Notepad ++不仅有语法高亮度显示,也有语法折叠功能,并且支持宏以及扩充基本功能的外挂模组.但是对Markdown支持不够. 这里通过插件与自定义语法让Notepad++变成一个Markdown书写工具. Markdown语法高亮 下载所需文件 因为通过GitHub下载一直超时,我就直接打包放在博客园了. 下载链接 导入语法规则 打开Notepad++,点击"语言" ,

APICloud全面支持WiFi真机同步和实时预览功能

APICloud工具插件包括APICloud Studio.Sublime Text和Webstorm全面为开发者提供iOS和Android平台真机同步调试功能,不仅可以通过USB方式进行APP真机同步功能,更新增WiFi真机同步和WiFi真机实时预览两大功能,方便开发者在开发过程中进行真机预览和调试,加快同步的速度和提高预览调试的效率.使用这三种工具插件中的WiFi真机实时预览和WiFi真机同步功能均一致.   WiFi真机实时预览:可以实时在真机上加载并预览指定页面的运行效果,方便开发者在真

LaTeX实时预览中文

参考资料:http://blog.sina.com.cn/s/blog_6ea58f530101aizw.html 功夫不负有心人,终于在经过艰苦卓绝的寻找之后,让我的Texpad实现了实时预览.此时的心情那叫一个澎湃:-) 先说一下我的解决方案(Texpad + CJK packages) Texpad是macos和ios上的一款LaTeX排版软件,这个软件的开发者基于TeX重写了一个TeX引擎,名为TexpadTeX,它的过人之处是做到了pdf实时预览,注意不是实时编译,速度相当之快~.CJ

ubuntu vim markdown 实时预览

vim-instant-markdown插件 该插件支持vim编辑markdown文件时实时预览,不需要手动做任何事情! 使用vim打开一个xxx.md文件,浏览器会自动打开一个预览网页,在编辑这个文件的过程中,浏览器会自动更新,并不用保存该文档. 安装vim-instant-markdown 1.安装node.js curl -sL https://deb.nodesource.com/setup_5.x | sudo -E bash -sudo apt-get install -y node

用 grunt-contrib-connect 构建实时预览开发环境 实时刷新

本文基本是参照着 用Grunt与livereload构建实时预览的开发环境 实操了一遍,直接实现能实时预览文件列表,内容页面.不用刷新页面了,这比以前开发网页程序都简单. 这里要用到的 Grunt 插件有 grunt-contrib-connect , 用来充当一个静态文件服务器,本身集成了 livereload 功能 grunt-contrib-watch , 监视文件的改变,然后执行指定任务,这里用来刷新  grunt serve 打开的页面 以下是个辅助的插件 load-grunt-tas

整合VIM和Graphviz,并且使用本办法实现实时预览

在编程或是整理知识的时候一直苦于没有一款可以帮助理清思路的工具. 在网上苦寻良久,终于找到了一款可心可意的小软件 -- Graphviz. 折腾了一番,终于可以凑合着用了. 现将折腾的成果记录于此以作备忘,当然如果能够抛砖引玉那就再好不过了. 因为本人主要使用 Windows 以下将用 Windows 环境为例进行介绍. First 于此处下载需要的版本:http://www.graphviz.org/Download..php 将下载好的 msi 文件或 zip 文件安装或解压,记下安装路径.

利用less监视模式实时预览样式刷新浏览器

[前言]此处介绍的方法只是我个人的用法,相信大家有更好更简洁的方式. 上次写到利用LiveReload解放F5.而且LiveReload可以编辑sass/less/stylus.但是可惜发现LiveReload在编译的时候不能抛错,这就很麻烦了,少了个标点less编译就不过,查找起来太麻烦. 我目前的解决方法: 利用Less自带的客户端开发模式(development). 在开发的时候less的编译还是利用客户端浏览器,即 <link rel="stylesheet/less"