iOS一步步实现一个高度自定义UIButton控件

=-=.jpg

需求背景

日常开发中UIButton的图片与标题默认的布局是固定的,是在水平方向左右排列。但是我们会经常需要更改imagetitle的位置来实现需求,这是个很常见的需求就不多说了。所以下面就来谈谈如何一步步的实现一个高度自定义的UIButton控件。

实现思路

默认情况下,在button有固定的宽高值的时候,imagetitle是以相对左右排列,整体居中于button来显示的,如果没有固定的宽高值,即大小自适应的情况下,整个UIButton将自动缩放到刚好可以容纳imagetitle的大小。如图所示:

自适应大小.png

了解UIButton的各个属性
在准备自定义之前,我们需要了解UIButton的各个属性都是怎么运用和实现的,因为要修改titleimage的位置与这些属性是密不可分的。
因为这些都是基本的开发知识,我就不再过多叙述,分别以图片来展示效果:

UIControlContentVerticalAlignment

UIControlContentVerticalAlignment各种效果.png

UIControlContentHorizontalAlignment

UIControlContentHorizontalAlignment各种效果.png

通过上面两张图可以清楚地看到UIControlContentVerticalAlignmentUIControlContentHorizontalAlignment在不同值下的效果,灰色背景的就是一个button的实际大小,center就是系统默认的值,明显的在两种fill值下,图片都出现了拉伸的情况,而且在水平fill下,图片并没有像垂直情况下水平铺满整个控件,imagetitle还重叠到了一起去。

UIEdgeInsets

UIButton的另一个重要的属性就是这个了,称之为偏移量,他分别有contentEdgeInsetsimageEdgeInsetstitleEdgeInsets三个相关属性。
默认情况下:

  • contentEdgeInsetstopleftbottomright都是相对于button本身,控制着imagetitle整体的偏移量;
  • imageEdgeInsetstopleftbottom相对于buttonright相对于title,控制着image的相对偏移量;
  • titleEdgeInsetstopbottomright相对于buttonleft相对于image,控制着title的相对偏移量;

我用一张图来解释一下:


偏移量.png

上图的正负值就代表偏移方向

我们想要的效果
写到这里,我们需要考虑一下我们的需求,我们并不是来分析button的实现原理,而是要实现一个自定义的button,自定义imagetitle的相对位置是最起码的要求。相信看到这里大家也知道我们只需要修改imageEdgeInsetstitleEdgeInsets的值就可以随意布局imagetitle的相对位置。比如左titleimage,上imagetitle
要实现这个需求有两种方式:

  • 新建一个UIButton的分类UIButton + xx,在layoutSubviews里修改imageEdgeInsetstitleEdgeInsets的值。这种方式可以简单实现我们的基本需求,但如果想要添加更多的自定义属性还需要通过runtime来实现,好处就是拥有调用系统API的舒爽,直接UIButton调用,没什么代码侵入性。
  • 封装一个继承自UIButtonCustomButton,可以自由添加自定义方法、属性,在layoutSubviews里重置imagetitleframe来实现不同的布局方式。

其实这两种方式都可以实现自定义的效果,具体选用哪个就看你自己的需求了,我这里就第二种方式来实现一下。

上面所说到的contentEdgeInsetsimageEdgeInsetstitleEdgeInsets默认值都是zero,但是我们现在假设他们都有一个默认值x

button简易图示.png

这里实现的思路就是通过self.bounds减去四周的偏移量先获取整个content的实际size,再由contentSize减去imagetitle的对应偏移量来获取imagetitle的实际size。总之就是先获取imagetitle的实际大小,然后根据不同的布局重置imagetitlexy坐标和bound,从而得到对应的frame,就可以实现自由布局了。

上面所说的是button有固定宽高值的情况,如果button的宽高自适应,即调用sizeToFit方法时,我们需要在- (CGSize)sizeThatFits:(CGSize)size内针对不同情况重新计算出buttonsize,不然的话,系统会根据imagetitle的大小默认返回它们左右排列的size,此时的size是错误的,如图:

没做适配的结果.png

具体的布局分析思路就是这些了,因为代码太多,就不在这里粘贴详细代码了,如果需要代码的可以在文章底部找到demo的下载链接,demo里面也有详细的注释说明。

但是,到这里我们只是自定义了imagetitle的相对布局,我们的目的是自定义整个UIButton,所以系统默认的点击效果,CALayer的所有默认动画都需要移除掉,替换成我们自定义的layer效果。比如说系统button的默认高亮状态下图片颜色也会加深,这个其实很恶心,所以我们应该移除掉,就像图下所示:

系统button高亮状态.png

ok,现在我们来整理一下需要的常用属性,分别为normalhighlighteddisabled这几种状态下的背景色,透明度变化,图片的tintColor,边框线的颜色,我们就针对这几个点进行修改。
下面粘贴几块代码段大概展示一下:
highlighted逻辑

- (void)setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];
    
    if (highlighted && !self.originBorderColor) {
        // 手指按在按钮上会不断触发setHighlighted:,所以这里做了保护,设置过一次就不用再设置了
        self.originBorderColor = [UIColor colorWithCGColor:self.layer.borderColor];
    }
    
    // 渲染背景色
    if (self.highlightedBackgroundColor || self.highlightedBorderColor) {
        [self adjustsButtonHighlighted];
    }
    // 如果此时是disabled,则disabled的样式优先
    if (!self.enabled) {
        return;
    }
    // 自定义highlighted样式
    if (self.adjustsButtonWhenHighlighted) {
        if (highlighted) {
            self.alpha = 0.5f;
        } else {
            [UIView animateWithDuration:0.25f animations:^{
                self.alpha = 1;
            }];
        }
    }
}

enabled逻辑

- (void)setEnabled:(BOOL)enabled {
    [super setEnabled:enabled];
    if (!enabled && self.adjustsButtonWhenDisabled) {
        self.alpha = 0.5f;
    } else {
        [UIView animateWithDuration:0.25f animations:^{
            self.alpha = 1;
        }];
    }
}

移除系统layer,添加自定义layer

- (void)adjustsButtonHighlighted {
    if (self.highlightedBackgroundColor) {
        if (!self.highlightedBackgroundLayer) {
            self.highlightedBackgroundLayer = [CALayer layer];
            [self.highlightedBackgroundLayer FS_removeDefaultAnimations];
            [self.layer insertSublayer:self.highlightedBackgroundLayer atIndex:0];
        }
        self.highlightedBackgroundLayer.frame = self.bounds;
        self.highlightedBackgroundLayer.cornerRadius = self.layer.cornerRadius;
        self.highlightedBackgroundLayer.backgroundColor = self.highlighted ? self.highlightedBackgroundColor.CGColor : [UIColor colorWithRed:1 green:1 blue:1 alpha:0].CGColor;
    }
    
    if (self.highlightedBorderColor) {
        self.layer.borderColor = self.highlighted ? self.highlightedBorderColor.CGColor : self.originBorderColor.CGColor;
    }
}

因为需要大量的自定义属性来代替系统默认属性,虽然我很想在这里解释每个属性的用处,但是太麻烦了,所以还是建议直接下载demo,配合代码看文章,代码有详细的注释

这里就直接展示一下demo的效果图:

FSCustomButtonDemo.gif

以前项目用到的时候,我也是直接网上找的一个库,不过那个库包含内容太多,很多都没用,所以我将其中的部分代码抽离了出来直接在项目中运用,效果还可以很稳定,所以最近抽时间将代码从项目中抽离封装了一下,写了一个demo上传在github,需要的可以直接前往下载:
FSCustomButtonDemo

文章和demo中涉及到的知识点:
有条线叫“一个像素”
CALayer
关于UIButton的UIEdgeInsets属性

如果对你有所帮助,就点个喜欢吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容