UIButton是我们经常用的UI控件,继承UIControl。这里将对UIButton的基本使用方法和自定义UIButton进行详细介绍。
一、UIBUtton基本知识介绍
对于我们学习一个新的控件、无外乎两种方法。第一种是在xcode中的.m文件查看该控件的属性和相关方法,第二种直接在storyBoard中拖入该控件,然后在Xcode中的属性检查器面板,查看该控件属性。这里选择第二种,形象生动。
无论学习什么控件,都需要把该控件的基本属性熟记于心。
1、Type
主要有以下几种
Custom
该属性允许开发者设计按钮外观、而不是系统默认样式,比如要自定义Button样式。System
该属性使得按钮呈现默认风格。Detail Disclosure
该属性使得按钮呈现一个详情图标。InfoLight
该属性使得按钮呈现一个图标,同Detail Disclosure。- InfoDark
该属性使得按钮呈现一个图标,同Detail Disclosure。 - ContactAdd
该属性使得按钮呈现一个图标。
总结:在我们使用Button过程中,一般直接选用Custom。
2、State Config
该属性用来设置按钮的的状态。按钮总共有4种状态:
默认状态:UIControlStateNormal
高亮状态:UIControlStateHighlighted
对应属性:highlighted
选中状态:UIControlStateSelected
对应属性:selected
禁用状态:UIControlStateDisabled
对应属性:enabled
备注:高亮和选中状态的区别:高亮指的是用户碰触该按钮的时候,按钮显示高亮效果、用户离开时按钮高亮效果消失;选中状态是需要开发者设置selected属性为YES时,才会展示某种效果、设置为NO时,效果消失。
3、Title
标题、默认使用Plain、不过多解释
4、Font
设置文本标题字体大小、不过多解释
5、TextColor
设置文本标题颜色、不过多解释
6、Shadow Color
文本标题阴影颜色、需要设置Shadow Offset
7、Image
如果给按钮设置image,前面设置的Title属性可能会不起作用,该按钮只会显示一张图片。如果还想显示文字,则需要设置titleEdgeInsets和imageEdgeInsets属性<后面的自定义button会详细讲解>。
8、Backgroud
设置按钮的背景图片、不会遮挡标题文字
9、Shadow Offset
设置阴影文本与正常文本的偏移量
Width:大于0,阴影文本相对右偏移,反之,左偏移
Height:大于0,阴影文本相对下偏移,反之,上偏移
10、Line Break
对button文本标题的截断、因为标题太长、显示不全。有以下三种模式:
Truncate Head : 对字符串多余对开头部分截断,用…代替截断部分。
Truncate Middle : 对字符串多余的中间部分截断,用…代替截断部分。
Truncate Tail : 对字符串多余的结尾部分截断,用…代替截断部分。
11、Edge和Inset
二者结合在一起使用才有意义、不是孤立存在的。
Edge指的是边界,Inset指的是边界间距。
下面自定义Button会详细介绍到。
二、改变button中图片和文字的位置
默认情况下设置按钮的图片和文字,展示效果是图片在右边、文字在左边。
相关代码如下:
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setImage:[UIImage imageNamed:@"qq.jpg"] forState:UIControlStateNormal];
[btn setTitle:@"title" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.layer.borderColor = [UIColor redColor].CGColor;
btn.layer.borderWidth = 1.0;
btn.frame = CGRectMake(100, 100, 200, 200);
[self.view addSubview:btn];
效果图如下:
接下来将设置图片上文字下、图片左文字右、图片下文字上三种模式。
我们会通过两种方法进行,第一种方法:通过UIEdgeInsets设置边距;第二种方法通过完全自定义的方法。最后,我们将会对比这两种方法的优缺点。
方法一:通过UIEdgeInsets设置边距
1、UIEdgeInsets设置边距
首先需要介绍UIEdgeInsets
typedef struct UIEdgeInsets {
CGFloat top, left, bottom, right; // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
} UIEdgeInsets;
这里的四个参数表示距离上边界、左边界、下边界、右边界的距离,默认都是零。此时图片和文字在按钮的正中间。
以前在设置这个参数时,有一个误解,比如说设置按钮图片的上边距为60、即UIEdgeInsetsMake(60, 0, 0, 0)。一直以为是下面这种效果:
其实应该是这种效果:top、left、bottom、right是相对于原来的位置而言的。
疑问:设置距离上边距60,为什么这里标注30???
解答:其实这个和按钮的contentHorizontalAlignment、contentVerticalAlignment两个属性有关<下面用button的content属性代替>;默认情况下,两个属性都是AlignmentCenter,此时imageView和titleLabel两个子控件实际移动距离是设置距离的一半(60 / 2);下面会详细介绍。
小结:
top :大于0、向下移动;反之向上移动
left:大于0、向右移动;反之向左移动
bottom: 大于0、向上移动;反之向下移动
right: 大于0、向左移动;反之向右移动
2、图片右文字左模式
思路:将imageView向右平移一个自身控件的宽度、将titleLabel向左平移一个自身控件宽度。
代码如下:
//3、获取控件宽度
CGFloat imageViewWidth = btn.imageView.frame.size.width;
CGFloat titleWidth = btn.titleLabel.frame.size.width;
//4、设置内边距
[btn setImageEdgeInsets:UIEdgeInsetsMake(0, imageViewWidth, 0, 0)];
[btn setTitleEdgeInsets:UIEdgeInsetsMake(0, 0, 0, titleWidth)];
效果如下:
此时效果图并不是我们想要的,就像上面提到的,这里是由于button的两个特殊属性引起的,此时需要将设置距离=自身宽度 * 2。
//4、设置内边距
[btn setImageEdgeInsets:UIEdgeInsetsMake(0, imageViewWidth * 2, 0, 0)];
[btn setTitleEdgeInsets:UIEdgeInsetsMake(0, 0, 0, titleWidth * 2)];
实际效果如下:
疑问:如果设置button中的两个属性为其它模式,移动距离是否还是设置距离的2倍???
解答:经过一翻实验发现:
a、如果将contentHorizontalAlignment设置为UIControlContentHorizontalAlignmentCenter
左右移动距离 = 设定距离 / 2;
其它模式下左右移动距离 = 设定距离。
b、如果将contentVerticalAlignment设置为UIControlContentVerticalAlignmentCenter
上下移动距离 = 设定距离 / 2;
其它模式下上下移动距离 = 设定距离。
另外,如果将imageView和titleLabel设置为以下四种情况,则在当前位置,二者无法进行左右互换:
下面写的代码默认button的content属性为center。
为了方便理解,可以下载代码,详情地址见文章最后。
3、图片上文字下模式
思路:将imageView向上移动(自身高度/2)、向右移动(自身宽度/2);将titleLabel向下移动(自身高度/2)、向左移动(自身宽度/2)。
//3、获取控件宽度
CGFloat imageViewWidth = btn.imageView.frame.size.width;
CGFloat imageViewHeight = btn.imageView.frame.size.height;
CGFloat titleWidth = btn.titleLabel.frame.size.width;
CGFloat titleHeight = btn.titleLabel.frame.size.height;
//4、设置内边距
[btn setImageEdgeInsets:UIEdgeInsetsMake(0, imageViewWidth, imageViewHeight, 0)];
[btn setTitleEdgeInsets:UIEdgeInsetsMake(titleHeight, 0, 0, titleWidth)];
效果如下图:
4、图片下文字上模式
思路:将imageView向下移动(自身高度/2)、向右移动(自身宽度/2);将titleLabel向上移动(自身高度/2)、向左移动(自身宽度/2)。
//3、获取控件宽度
CGFloat imageViewWidth = btn.imageView.frame.size.width;
CGFloat imageViewHeight = btn.imageView.frame.size.height;
CGFloat titleWidth = btn.titleLabel.frame.size.width;
CGFloat titleHeight = btn.titleLabel.frame.size.height;
//4、设置内边距
[btn setImageEdgeInsets:UIEdgeInsetsMake(imageViewHeight, imageViewWidth, 0, 0)];
[btn setTitleEdgeInsets:UIEdgeInsetsMake(0, 0, titleHeight, titleWidth)];
效果图如下:
补充:默认情况下,imageView和titleLabel控件二者中心点与UIButton的中心点重合。
5、此方法使用局限性
当图片过大的时候,当前按钮只会显示一张图片;因为我们开发人员无法控制按钮内部的imageView和titleLabel两个子控件大小、只能移动位置。
解释:
1.当button.width < image.width时,只显示被压缩后的图片,图片是按fillXY的方式压缩。
2.当button.width > image.width,且 button.width < (image.width + text.width)时,图片正常显示,文本被压缩。
3.当button.width > (image.width + text.width),两者并列默认居中显示,可通过button的属性contentHorizontalAlignment改变对齐方式。
方法二、自定义Button
这个也可以分为以下两种方式:
a、重写系统Rect方法
思路:设置内部imageView子控件的高度=宽度=当前按钮的高度;设置内部titleLabel的高度=当前按钮的高度、宽度=(当前按钮的宽度 - 当前按钮的高度)。
备注:这里可以随意设置imageView和titleLabel的frame,这里只是提供一种思路。这个方法不是本文重点,重点是第二种方式。
重写系统的两个方法:
-(CGRect)titleRectForContentRect:(CGRect)contentRect
-(CGRect)imageRectForContentRect:(CGRect)contentRect
代码如下:
//返回标题的区域
- (CGRect)titleRectForContentRect:(CGRect)contentRect
{
CGFloat titleWidth = contentRect.size.width - contentRect.size.height;
return CGRectMake(0, 0, titleWidth, contentRect.size.height);
}
//返回图片等区域
- (CGRect)imageRectForContentRect:(CGRect)contentRect
{
CGFloat imageHeight = contentRect.size.height;
CGFloat imageWidth = imageHeight;
return CGRectMake(contentRect.size.width - imageWidth, 0, imageWidth, imageHeight);
}
b、重写系统layoutSubviews方法
思路:这个方法主要是重写系统的layoutSubviews、重新设置imageView、titleLabel、button自身宽高。
这里对按钮重新进行了封装,方便使用,详情见demo。
三、扩大Button的响应区域
项目中遇到一个需求,那就是不改变按钮的大小,但是响应区域要变大。经过一翻查找,终于找到了解决办法,特此记录一下。
主要利用ios的事件响应链,重写系统方法、拦截事件,扩大响应区域。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden)
{
return [super pointInside:point withEvent:event];
}
CGRect relativeFrame = self.bounds;
CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
return CGRectContainsPoint(hitFrame, point);
}
详细见gitHub代码:
https://github.com/yscMichael/CustomizeButton