UIButton 自定义布局
前阵子开始忙公司项目,可项目忙到一半上面人说这个项目不搞了。很无奈,年前白忙活啦。原想着等项目上线,如果收益不错,还可以多发点年终奖,年会上发个优秀员工奖红包也会大一点,哈哈哈,原谅我是个俗人。废话不多说了,今天来分享一个闲着没事造的一个小轮子 自定义UIButton
调研结果
在网上去调查如下几个代表
总结大体分为两种
- 设置UIButton 的 imageEdgeInsets 和 titleEdgeInsets,对于这种方式确实能实现效果,但是如果文字发生变化或者图片大小发生变化,就得重新计算 imageEdgeInsets 和 titleEdgeInsets。而且每当要用到这个自定义Button的时候都得写一遍这个长长的代码,累不累,我觉得这种方法不可取。
- 写一个XXXButton 继承自 UIButton ,然后重写 layoutSubviews 方法,然后在 layoutSubviews 方法中布局 titleLabel 和 imageView。无力吐槽,自己看一下代码,首先代码完整度先不说,本身对UIButton 的独有的API都不了解。
- (void)layoutSubviews{
[super layoutSubviews];
CGRect imageRect = self.imageView.frame;
imageRect.origin.x = self.frame.size.width * 0.5 - imageRect.size.width * 0.5;
imageRect.origin.y = self.frame.size.height * 0.5 - imageRect.size.height;
CGRect titleRect = self.titleLabel.frame;
titleRect.origin.x = self.frame.size.width * 0.5 - titleRect.size.width * 0.5;
titleRect.origin.y = self.frame.size.height * 0.5 ;
self.imageView.frame = imageRect;
self.titleLabel.frame = titleRect;
}
在这里面 BAButton 是最优秀的
下面来看一下官方 UIButton 独有的API
// these return the rectangle for the background (assumes bounds), the content (image + title) and for the image and title separately. the content rect is calculated based
// on the title and image size and padding and then adjusted based on the control content alignment. there are no draw methods since the contents
// are rendered in separate subviews (UIImageView, UILabel)
- (CGRect)backgroundRectForBounds:(CGRect)bounds;
- (CGRect)contentRectForBounds:(CGRect)bounds;
- (CGRect)titleRectForContentRect:(CGRect)contentRect;
- (CGRect)imageRectForContentRect:(CGRect)contentRect;
backgroundRectForBounds
官方描述
The default implementation of this method returns the value in the bounds parameter. This rectangle represents the area in which the button draws its standard background content. Subclasses that provide custom background adornments can override this method and return a modified bounds rectangle to prevent the button from drawing over any custom content.
此方法的默认实现将返回bounds参数中的值。此矩形表示按钮绘制其标准背景内容的区域。提供自定义背景装饰的子类可以覆盖此方法并返回一个修改过的边界矩形,以防止按钮绘制任何自定义内容。
contentRectForBounds 方法,这里返回值后面要用到
官方描述
The content rectangle is the area needed to display the image and title including any padding and adjustments for alignment and other settings.
内容矩形是显示图像和标题所需的区域,包括用于对齐和其他设置的任何填充和调整。
titleRectForContentRect 方法,返回标题显示的区域
官方描述
The rectangle in which the receiver draws its title.
接收方在其中绘制标题的矩形。
imageRectForContentRect 方法,返回图片内容显示的区域
官方描述
The rectangle in which the receiver draws its image.
接收器在其中绘制图像的矩形。
有了上面几个知识点,下面我们就可以来自定义Button布局了
自定义 UIButton布局正确的方式
自定义布局可以有3种
-
写一个自定义view 继承自 UIButton,然后通过重写
titleRectForContentRect
方法和imageRectForContentRect
方法来达到自定义布局。推荐使用 -
写一个 UIButton 的一个扩张类,利用 OC 分类方法优先调用的特性,实现
titleRectForContentRect
方法和imageRectForContentRect
方法,但是这种方式会影响到工程内的所有 UIButton,如果是多人开发的话,有点不道德。所以不建议使用 -
写一个自定义 View 继承自 UIView,里面控件想放什么就放什么,比较灵活,但是实现控件的不同状态还是比较麻烦。如果你就是喜欢搞麻烦的事情,可以使用这种方式。
我采用的是第一种方式
代码演示
写一个 JBCButton 继承自UIButton
@interface JBCButton : UIButton
@end
确定对外API
显示风格分为图片在上文字在下、文字在上图片在下、图片在左文字在右,文字在左图片在右
WechatIMG1.jpeg用一组枚举来表示
typedef NS_ENUM(NSInteger, JBCButtonImagePosition) {
JBCButtonImagePositionLeft = 0, // 图片在文字左侧
JBCButtonImagePositionRight = 1, // 图片在文字右侧
JBCButtonImagePositionTop = 2, // 图片在文字上侧
JBCButtonImagePositionBottom = 3 // 图片在文字下侧
};
考虑到加载 网络大图的情况,在button中显示的图片的需要拉伸,给一个imageSize
属性来确定image 的宽高
用一个 imageTitleSpace
属性来表示 图片和标题之间的距离
JBCButton.h 中的整体代码如下
typedef NS_ENUM(NSInteger, JBCButtonImagePosition) {
JBCButtonImagePositionLeft = 0, // 图片在文字左侧
JBCButtonImagePositionRight = 1, // 图片在文字右侧
JBCButtonImagePositionTop = 2, // 图片在文字上侧
JBCButtonImagePositionBottom = 3 // 图片在文字下侧
};
/** 上下布局button */
@interface JBCButton : UIButton
// Button 的样式 默认为 JBCButtonImagePositionLeft
@property (nonatomic) JBCButtonImagePosition imagePosition;
// 图片和文字之间的间距
@property (nonatomic, assign) CGFloat imageTitleSpace;
// 图片区域的大小,默认为图片的大小
@property (nonatomic, assign) CGSize imageSize;
//快捷UIButton normal 状态下属性的快捷访问属性,实质上就是 set/get 方法
@property (nonatomic, copy) UIFont *font;
@property (nonatomic, copy) UIColor *titleColor;
@property (nonatomic, copy) UIImage *image;
@property (nonatomic, copy) NSString *title;
//自定义button 要这个方法何用,干脆禁掉
+ (instancetype)buttonWithType:(UIButtonType)buttonType UNAVAILABLE_ATTRIBUTE;
@end
JBCButton.m 的整体实现
@interface JBCButton () {
CGRect _imageReact;
CGRect _titleReact;
}
@end
@implementation JBCButton
@dynamic font;
@dynamic titleColor;
@dynamic title;
@dynamic image;
- (void)setImagePosition:(JBCButtonImagePosition)imagePosition {
_imagePosition = imagePosition;
[self setNeedsLayout];
}
- (void)setImageTitleSpace:(CGFloat)imageTitleSpace {
_imageTitleSpace = imageTitleSpace;
[self setNeedsLayout];
}
- (void)setImageSize:(CGSize)imageSize {
_imageSize = imageSize;
[self setNeedsLayout];
}
//核心代码部分----------------------------------------------------
- (CGRect)titleRectForContentRect:(CGRect)contentRect {
return _titleReact;
}
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
if (self.imageSize.width > 0 || self.imageSize.height > 0) {
CGFloat imageW = self.imageSize.width;
CGFloat imageH = self.imageSize.height;
if (self.imagePosition == JBCButtonImagePositionLeft) {
CGSize titleSize = [self.currentTitle sizeWithAttributes:@{NSFontAttributeName: self.titleLabel.font}];
//内容总宽度
CGFloat totalWidth = titleSize.width + imageW + self.imageTitleSpace;
CGFloat imageY = (contentRect.size.height - imageH)/2.0;
CGFloat imageX = (contentRect.size.width - totalWidth)/2.0;
_imageReact = CGRectMake(imageX, imageY, imageW, imageH);
CGFloat titleX = imageX + imageW + self.imageTitleSpace;
CGFloat titleY = (contentRect.size.height - titleSize.height)/2.0;
CGFloat titleW = titleSize.width;
CGFloat titleH = titleSize.height;
_titleReact = CGRectMake(titleX, titleY, titleW, titleH);
return _imageReact;
} else if (self.imagePosition == JBCButtonImagePositionTop) {
CGSize titleSize = [self.currentTitle sizeWithAttributes:@{NSFontAttributeName: self.titleLabel.font}];
//内容总高度
CGFloat totalHeight = titleSize.height + imageH + self.imageTitleSpace;
CGFloat imageY = (contentRect.size.height - totalHeight)/2.0;
CGFloat imageX = (contentRect.size.width - imageW)/2.0;
_imageReact = CGRectMake(imageX, imageY, imageW, imageH);
CGFloat titleX = (contentRect.size.width - titleSize.width)/2.0;
CGFloat titleY = imageY + imageH + self.imageTitleSpace;
CGFloat titleW = titleSize.width;
CGFloat titleH = titleSize.height;
_titleReact = CGRectMake(titleX, titleY, titleW, titleH);
return _imageReact;
} else if (self.imagePosition == JBCButtonImagePositionRight) {
CGSize titleSize = [self.currentTitle sizeWithAttributes:@{NSFontAttributeName: self.titleLabel.font}];
//内容总宽度
CGFloat totalWidth = titleSize.width + imageW + self.imageTitleSpace;
CGFloat imageY = (contentRect.size.height - imageH)/2.0;
CGFloat imageX = (contentRect.size.width - totalWidth)/2.0 + titleSize.width + self.imageTitleSpace;
_imageReact = CGRectMake(imageX, imageY, imageW, imageH);
CGFloat titleX = (contentRect.size.width - totalWidth)/2.0;
CGFloat titleY = (contentRect.size.height - titleSize.height)/2.0;
CGFloat titleW = titleSize.width;
CGFloat titleH = titleSize.height;
_titleReact = CGRectMake(titleX, titleY, titleW, titleH);
return _imageReact;
} else {
CGSize titleSize = [self.currentTitle sizeWithAttributes:@{NSFontAttributeName: self.titleLabel.font}];
//内容总高度
CGFloat totalHeight = titleSize.height + imageH + self.imageTitleSpace;
CGFloat imageY = (contentRect.size.height - totalHeight)/2.0 + titleSize.height + self.imageTitleSpace;
CGFloat imageX = (contentRect.size.width - imageW)/2.0;
_imageReact = CGRectMake(imageX, imageY, imageW, imageH);
CGFloat titleX = (contentRect.size.width - titleSize.width)/2.0;
CGFloat titleY = (contentRect.size.height - totalHeight)/2.0;
CGFloat titleW = titleSize.width;
CGFloat titleH = titleSize.height;
_titleReact = CGRectMake(titleX, titleY, titleW, titleH);
return _imageReact;
}
} else {
return [super imageRectForContentRect:contentRect];
}
}
- (void)setImage:(UIImage *)image forState:(UIControlState)state {
if (self.imageSize.height <= 0 && self.imageSize.height <= 0) {
self.imageSize = CGSizeMake(image.size.width > self.frame.size.width ? self.frame.size.width : image.size.width,
image.size.width > self.frame.size.height ? self.frame.size.height : image.size.height);
}
[super setImage:image forState:state];
}
//核心代码部分----------------------------------------------------
- (void)setFont:(UIFont *)font {
self.titleLabel.font = font;
}
- (UIFont *)font {
return self.titleLabel.font;
}
- (UIColor *)titleColor {
return self.currentTitleColor;
}
- (void)setTitleColor:(UIColor *)titleColor {
[self setTitleColor:titleColor forState:self.state];
}
- (UIImage *)image {
return self.currentImage;
}
- (void)setImage:(UIImage *)image {
[self setImage:image forState:self.state];
}
- (NSString *)title {
return self.currentTitle;
}
- (void)setTitle:(NSString *)title {
[self setTitle:title forState:self.state];
}
整体代码大概就 150 行的样子
测试一下这个轮子
测试代码如下
@interface ViewController ()
@property (nonatomic, weak) JBCButton *btn1;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
JBCButton *btn1 = [[JBCButton alloc] initWithFrame:CGRectMake(100, 100, 250, 200)];
btn1.center = self.view.center;
btn1.title = @"点击改变风格";
btn1.image = [UIImage imageNamed:@"icon"];
btn1.backgroundColor = UIColor.blueColor;
btn1.imageSize = CGSizeMake(53, 53);
btn1.imageTitleSpace = 10;
btn1.imagePosition = JBCButtonImagePositionLeft;
[btn1 addTarget:self
action:@selector(clicked:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn1];
self.btn1 = btn1;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 300, 40)];
label.center = CGPointMake(self.view.center.x, 100);
label.text = @"点击屏幕改变文字和图片的间距";
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = UIColor.greenColor;
[self.view addSubview:label];
}
- (void)clicked:(JBCButton *)btn {
if (btn.imagePosition >= 3) {
btn.imagePosition = 0;
} else {
btn.imagePosition += 1;
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.btn1.imageTitleSpace = self.btn1.imageTitleSpace > 20 ? 20 : 50;
}
运行结果
Jan-28-2019 16-56-06.gif以这种方式来布局 Button 同时还支持 masonry 布局方式