逻辑思路:
button设置图片和文字后后再设置titleEdgeInsets
属性和 imageEdgeInsets
属性实现button的上图下文,上文下图,左图右文,右图左文的重新排列(自由设置图文间距)
UIEdgInsets
官方解释:
Edge inset values are applied to a rectangle to shrink or expand the area represented by that rectangle. Typically, edge insets are used during view layout to modify the view’s frame. Positive values cause the frame to be inset (or shrunk) by the specified amount. Negative values cause the frame to be outset (or expanded) by the specified amount.
说人话就是:UIEdgInsets是用来扩大或者缩小控件的矩形区域,通常在视图布局期间修改视图框架,正值缩小视图框架, 负值扩大视图框架
左文右图
默认情况下,button的image和label是紧贴着居中的,那如果想要image在右边,label在左边应该怎么办呢?
答案就是:
self.imageEdgeInsets = UIEdgeInsetsMake(0, labelWidth + spacing/2, 0, -(labelWidth + spacing/2));
self.titleEdgeInsets = UIEdgeInsetsMake(0, -(imageHeight + spacing/2), 0, imageHeight + spacing/2);
其实就是这一句:This property is used only for positioning the image during layout
其实titleEdgeInsets属性和 imageEdgeInsets属性只是在画这个button出来的时候用来调整image和label位置的属性,并不影响button本身的大小。
UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)
它们只是image和button相较于原来位置的偏移量,那什么是原来的位置呢?就是没有设置edgeInset时候的位置了
imageEdgeInsets,titleEdgeInsets默认为0,spacing为图片文字间隔
如果image在右边,label在左边,二者的上下的偏移量相较于原来的位置没有改变,为0。
左右偏移量的计算:
偏移的距离:
imageOffset
= | 初始X - 修改后X | = showLabW + spacing/2
备注
- showLabW 代表titleLable在button内部的初始(original)显示宽度,当button的宽度不够时,titleLable会被压缩,此时宽度小于完全显示时的真实宽度trueLabW,参见后图。
- 宽度不够的情况下titleLable会优先被压缩,imageView的宽度一般是正确的
- 此处认为showLabH = trueLabH,没有考虑label多行显示的情形
正负值判断:
假设只改变image的一条边距的偏移量,如果image缩小,偏移量为正,反之为负;
self.imageEdgeInsets
= UIEdgeInsetsMake(0, showLabW + spacing/2, 0, -(showLabW + spacing/2));
label计算过程同理:
self.titleEdgeInsets
= UIEdgeInsetsMake(0, -(imgW + spacing/2), 0, imgW + spacing/2);
上图下文
上图下文和上文下图计算方式一样(实际四种模式计算方式都一样),下面就以上图下文的计算为例。
偏移距离:
imageOffsetX
= labW/2
imageOffsetY
= labH/2 + spacing/2
labelOffsetX
= imgW/2
labelOffsetY
= imgH/2 + spacing/2
提示:
在实际应用中发现这种情况在button的宽度大于 ImageWidth+lableWidth是没有问题的,但是当小于的时候就不行了
为什么呢
比较上两张图发现lable中心点的位置是正确的,只是左右的边缘的偏移量(决定宽度)没有计算好,ImageView的中心点位置是错误的,大小是正确的。
CGFloat labelWidth = [self.titleLabel.text sizeWithAttributes:@{NSFontAttributeName:self.titleLabel.font}].width;
CGFloat labelHeight = [self.titleLabel.text sizeWithAttributes:@{NSFontAttributeName:self.titleLabel.font}].height;
这里计算的是lable的真实宽度,而button宽度不够时,label会被压缩的显示宽度是小于真实宽度的,我们在重新排列的时候计算Image中心点的位置应该用lable的显示宽度而不是真实宽度。
同样,计算lable左右边缘的偏移量也应该把lable的真实宽度考虑进去,详情见下图
X1,X2分别为lable的左右边缘x值。
偏移距离:
//image中心移动的x距离
CGFloat imageOffsetX = showLabW /2 ;
//image中心移动的y距离
CGFloat imageOffsetY = showLabH / 2 + spacing / 2;
//label左边缘移动的x距离
CGFloat labelOffsetX1 = imgW/2 - showLabW/2 + trueLabW/2;
//label右边缘移动的x距离
CGFloat labelOffsetX2 = imgW/2 + showLabW/2 - trueLabW/2;
//label中心移动的y距离
CGFloat labelOffsetY = imgH / 2 + spacing / 2;
注意:当button宽度够大时 showLabW = trueLabW
此时labelOffsetX1 = labelOffsetX2 = imgW/2
和上面情况一样一样的,也就是说这种计算方法也适用于上面的情景
正负值判断后结果为:
self.imageEdgeInsets
= UIEdgeInsetsMake(-imageOffsetY, imageOffsetX, imageOffsetY, -imageOffsetX);
self.titleEdgeInsets
= UIEdgeInsetsMake(labelOffsetY, -labelOffsetX1, -labelOffsetY, labelOffsetX2);
上文下图
偏移距离:
//image中心移动的x距离
CGFloat imageOffsetX = showLabW /2 ;
//image中心移动的y距离
CGFloat imageOffsetY = showLabH / 2 + spacing / 2;
//label左边缘移动的x距离
CGFloat labelOffsetX1 = imgW/2 - showLabW/2 + trueLabW/2;
//label右边缘移动的x距离
CGFloat labelOffsetX2 = imgW/2 + showLabW/2 - trueLabW/2;
//label中心移动的y距离
CGFloat labelOffsetY = imgH / 2 + spacing / 2;
结果竟然跟上面一样,说明这种方法有点笨了,肯定有不同的理解方法与解决方案,在这里就抛砖引玉了,欢迎交流。
正负值判断后结果为:
self.imageEdgeInsets
= UIEdgeInsetsMake(imageOffsetY, imageOffsetX, -imageOffsetY, -imageOffsetX);
self.titleEdgeInsets
= UIEdgeInsetsMake(-labelOffsetY, -labelOffsetX1, labelOffsetY, labelOffsetX2);
实际应用
这些处理是专门对Button进行的,最好的应用场景当然是写到Button的Category
里面,方便外面调用。
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, ZCImagePosition) {
ZCImagePositionLeft, //图片在左,标题在右,默认风格
ZCImagePositionRight, //图片在右,标题在左
ZCImagePositionTop, //图片在上,标题在下
ZCImagePositionBottom //图片在下,标题在上
};
NS_ASSUME_NONNULL_BEGIN
/**
默认情况下,imageEdgeInsets和titleEdgeInsets都是0。先不考虑height,
if (button.width小于imageView上image的width){图像会被压缩,文字不显示}
if (button.width < imageView.width + label.width){图像正常显示,文字显示不全,这种情况下竖直排列时,要特别注意,已解决}
//www.greatytc.com/p/f521505beed9
if (button.width >= imageView.width + label.width){图像和文字都居中显示,imageView在左,label在右,中间没有空隙}
*/
@interface UIButton (EdgeInsets)
/**
* 利用UIButton的titleEdgeInsets和imageEdgeInsets来实现文字和图片的自由排列
* 注意:这个方法需要在设置图片和文字之后才可以调用,且button的大小要大于 图片大小spacing
*
* @param spacing 图片和文字的间隔
*/
- (void)setImagePosition:(ZCImagePosition)postion spacing:(CGFloat)spacing;
/**
* 利用UIButton的titleEdgeInsets和imageEdgeInsets来实现文字和图片的自由排列
* 注意:这个方法需要在设置图片和文字之后才可以调用,且button的大小要大于 图片大小+文字大小+spacing
*
* @param margin 图片、文字离button边框的距离
*/
- (void)setImagePosition:(ZCImagePosition)postion WithMargin:(CGFloat )margin;
@end
NS_ASSUME_NONNULL_END
#import "UIButton+EdgeInsets.h"
@implementation UIButton (EdgeInsets)
#pragma mark -- 按钮上图文混排
- (void)setImagePosition:(ZCImagePosition)postion spacing:(CGFloat)spacing {
self.titleEdgeInsets = self.imageEdgeInsets = UIEdgeInsetsZero;
CGFloat imgW = self.imageView.image.size.width;
CGFloat imgH = self.imageView.image.size.height;
CGSize showLabSize = self.titleLabel.bounds.size;
CGFloat showLabW = showLabSize.width;
CGFloat showLabH = showLabSize.height;
CGSize trueSize = [self.titleLabel sizeThatFits:CGSizeMake(MAXFLOAT, MAXFLOAT)];
CGFloat trueLabW = trueSize.width;
//image中心移动的x距离
CGFloat imageOffsetX = showLabW/2 ;
//image中心移动的y距离
CGFloat imageOffsetY = showLabH/2 + spacing/2;
//label左边缘移动的x距离
CGFloat labelOffsetX1 = imgW/2 - showLabW/2 + trueLabW/2;
//label右边缘移动的x距离
CGFloat labelOffsetX2 = fabs(imgW/2 + showLabW/2 - trueLabW/2);
//label中心移动的y距离
CGFloat labelOffsetY = imgH/2 + spacing/2;
switch (postion) {
case ZCImagePositionLeft:
self.imageEdgeInsets = UIEdgeInsetsMake(0, -spacing/2, 0, spacing/2);
self.titleEdgeInsets = UIEdgeInsetsMake(0, spacing/2, 0, -spacing/2);
break;
case ZCImagePositionRight:
self.imageEdgeInsets = UIEdgeInsetsMake(0, showLabW + spacing/2, 0, -(showLabW + spacing/2));
self.titleEdgeInsets = UIEdgeInsetsMake(0, -(imgW + spacing/2), 0, imgW + spacing/2);
break;
case ZCImagePositionTop:
self.imageEdgeInsets = UIEdgeInsetsMake(-imageOffsetY, imageOffsetX, imageOffsetY, -imageOffsetX);
self.titleEdgeInsets = UIEdgeInsetsMake(labelOffsetY, -labelOffsetX1, -labelOffsetY, labelOffsetX2);
break;
case ZCImagePositionBottom:
self.imageEdgeInsets = UIEdgeInsetsMake(imageOffsetY, imageOffsetX, -imageOffsetY, -imageOffsetX);
self.titleEdgeInsets = UIEdgeInsetsMake(-labelOffsetY, -labelOffsetX1, labelOffsetY, labelOffsetX2);
break;
default:
break;
}
}
/**根据图文距边框的距离调整图文间距*/
- (void)setImagePosition:(ZCImagePosition)postion WithMargin:(CGFloat )margin{
if (postion == ZCImagePositionLeft || postion == ZCImagePositionRight) {
CGFloat imageWith = self.imageView.image.size.width;
CGFloat labelWidth = [self.titleLabel sizeThatFits:CGSizeMake(MAXFLOAT, MAXFLOAT)].width;
CGFloat spacing = self.bounds.size.width - imageWith - labelWidth - 2*margin;
[self setImagePosition:postion spacing:spacing];
}else {
CGFloat imageHeight = self.imageView.image.size.height;
CGFloat labelHeight = [self.titleLabel sizeThatFits:CGSizeMake(MAXFLOAT, MAXFLOAT)].height;
CGFloat spacing = self.bounds.size.height - imageHeight - labelHeight - 2*margin;
[self setImagePosition:postion spacing:spacing];
}
}
@end
注意事项
- 在设置图片和文字之后,并且button的size不能为0调用本方法才能计算出正确的偏移量。
- 每次修改button的图片或文字后都要再调用一下该方法,否则排版可能会混乱。