系列:iOS开发-从扩展UIButton到自定义控件

系列:iOS开发-从扩展UIButton到自定义控件

我们在做iOS开发的时候,往往要制作一些跟系统控件不一样的自定义控件,
比如我们会定义一个图片在上面,文字在下面的按钮, 比如我们会定义一个复杂的控件,有点击,有长按,有拖拽等手势...

往往我们就会想到使用系统已经有的控件来做二次封装
当然自己封装控件就会有很多的方式,
比如实现的效果大致的是一个按钮,那么我们就会采用继承UIButton的方式,
又有可能继承不能够满足需求,我们可能更原始,直接继承UIView或者NsObject或者UIControl等比较偏底层的基类来实现.总之,我们的自定义控件肯定是能够通过很多方式来实现.

就拿上面的例子来说,我们要求创建一个图片在上,文字在下的按钮,我们会有多少方式?
多一点的方式调整UIButton的内部的imageView和label的位置,
比如调用

@property(nonatomic)          UIEdgeInsets contentEdgeInsets;
@property(nonatomic)          UIEdgeInsets titleEdgeInsets;
@property(nonatomic)          UIEdgeInsets imageEdgeInsets;

的属性来调整其位置和间距等等...

亦或者我们可能会创建一个类继承UIButton,在其上面添加一个新的UIImageView和UILabel,生成一个新的按钮控件,设置图片和文字等都采用自定义的方式,但是这样的话可能就会失去按钮点击高亮变暗等等原始的效果.当然我们可能也会并不在意.
再复杂点的,我们创建继承UIView或者UIControl的类来实现我们所需要的效果,当然这个实现需求可能会要求很高.

通过很多个应用观察来看,我们会发现很多现象,虽然在移动端的按钮会有很多不同的呈现状态,但是绝大部分都是图片和文字加在一起是居中的效果,都是比较规则的呈现,
这些效果绝大部分的呈现多为图片和文字的相对位置,比如图片在左文字在右,图片在右文字在左,图片在上文字在下,图片在下文字在上.
大致的会分成这4种样式,中间或者会有某些间距
那么我们改如何自定义呢?
设置偏移在简单需求中很奏效,因为简单实用,
我们可以从这个方向来考虑,但是我们又不应该破坏这个属性,因为你设置了这个属性,之后再想调整就会很麻烦..

这里我们就可能会关注到某一个方法

- (void)layoutSubviews;

layoutSubviews在以下情况下会被调用:

1、init初始化不会触发layoutSubviews

但是是用initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发

2、addSubview会触发layoutSubviews

3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化

4、滚动一个UIScrollView会触发layoutSubviews

5、旋转Screen会触发父UIView上的layoutSubviews事件

6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

这样的效果我们能够想到什么?
在苹果的官方文档中强调:

  You should override this method only if the autoresizing behaviors of the subviews do not offer the behavior you want.

layoutSubviews, 当我们在某个类的内部调整子视图位置时,需要调用。

这个是布局界面的时候会自动调用的方法,至于调用时间上面已经列出.
那么只有我们自己写的控件才会调用吗? 其实系统的控件也是跟我们一样的,
那么我们能否这样的思路?
创建一个继承自UIButton的按钮,然后重写layoutSubviews方法, 干两件事,
第一件, [super layoutSubviews]; 让父类先布局结束,防止出现想不到的事件,
第二件,调整子视图的位置,达到我们的效果.

我们可以这样简单的写下效果

#import "XLXButton.h"

@implementation XLXButton


-(void)layoutSubviews{
    [super layoutSubviews];
    
    [self setTitleEdgeInsets:UIEdgeInsetsZero];
    
    [self.titleLabel sizeToFit];
    CGRect labelFrame = self.titleLabel.frame;
    
    [self.imageView sizeToFit];
    CGRect imageFrame = self.imageView.frame;
    
    imageFrame.origin.x = (self.frame.size.width-imageFrame.size.width)*0.5;
    imageFrame.origin.y = (self.frame.size.height-imageFrame.size.height-labelFrame.size.height)*0.5;
    self.imageView.frame = imageFrame;
    
    labelFrame.origin.x = (self.frame.size.width-labelFrame.size.width)*0.5;;
    labelFrame.origin.y = imageFrame.origin.y+imageFrame.size.height;
    self.titleLabel.frame = labelFrame;
}

@end
这里写图片描述

很轻松的我们就创建了一个图片在上文字在下的按钮,那么这个按钮所有的其他的属性还是保持不变,我们一样操作,点击高亮,调节偏移....

但是细心的你应该会发现图片和文字挨的太紧了,所以我们可以在创建的时候添加一个间距,并且将其暴露出来,可以自由设置,如果设置了就用设置的值,没有设置就用一个默认值或者干脆就这样.
于是代码稍稍进化

#import <UIKit/UIKit.h>

@interface XLXButton : UIButton

@property (assign, nonatomic) CGFloat customSpace;

@end



#import "XLXButton.h"

@implementation XLXButton


-(void)layoutSubviews{
    [super layoutSubviews];
    
    self.customSpace = self.customSpace ? self.customSpace : 5;
    
    [self setTitleEdgeInsets:UIEdgeInsetsZero];
    
    [self.titleLabel sizeToFit];
    CGRect labelFrame = self.titleLabel.frame;
    
    [self.imageView sizeToFit];
    CGRect imageFrame = self.imageView.frame;
    
    imageFrame.origin.x = (self.frame.size.width-imageFrame.size.width)*0.5;
    imageFrame.origin.y = (self.frame.size.height-imageFrame.size.height-labelFrame.size.height-self.customSpace)*0.5;
    self.imageView.frame = imageFrame;
    
    labelFrame.origin.x = (self.frame.size.width-labelFrame.size.width)*0.5;;
    labelFrame.origin.y = imageFrame.origin.y+imageFrame.size.height+self.customSpace;
    self.titleLabel.frame = labelFrame;
}

@end

这样我们创建的按钮可以不动代码的发生一点化学变化


这里写图片描述

再然后我们就自然的想到,既然上可以,那么左右下呢?
于是代码不自觉的你就能够自主添加

#import <UIKit/UIKit.h>

/**
 按钮的样式
 
 - XLXButtonCustomStyleNomal:    普通样式
 - XLXButtonCustomStylePicTop:   图片在上文字在下
 - XLXButtonCustomStylePicLeft:  图片在左文字在右
 - XLXButtonCustomStylePicDown:  图片在下文字在上
 - XLXButtonCustomStylePicRight: 图片在右文字在左
 */
typedef NS_ENUM(NSUInteger, XLXButtonCustomStyle) {
    XLXButtonCustomStyleNomal = 0,
    XLXButtonCustomStylePicTop,
    XLXButtonCustomStylePicLeft,
    XLXButtonCustomStylePicDown,
    XLXButtonCustomStylePicRight,
};

@interface XLXButton : UIButton

/**
 自定义样式(nomal为系统原本的样式)
 */
@property (assign, nonatomic) XLXButtonCustomStyle xlx_customstyle;
///自定义间距(nomal下无效)
@property (assign, nonatomic) CGFloat xlx_customSpace;

@end





#import "XLXButton.h"

@implementation XLXButton


/**
 重新布局button的内容
 */
-(void)layoutSubviews{
    
    [super layoutSubviews];
    
    switch (self.xlx_customstyle) {
        case XLXButtonCustomStylePicTop:
        {
            [self setTitleEdgeInsets:UIEdgeInsetsZero];
            
            [self.titleLabel sizeToFit];
            CGRect labelFrame = self.titleLabel.frame;
            
            [self.imageView sizeToFit];
            CGRect imageFrame = self.imageView.frame;
            
            imageFrame.origin.x = (self.frame.size.width-imageFrame.size.width)*0.5;
            imageFrame.origin.y = (self.frame.size.height-imageFrame.size.height-labelFrame.size.height-self.xlx_customSpace)*0.5;
            self.imageView.frame = imageFrame;
            
            labelFrame.origin.x = (self.frame.size.width-labelFrame.size.width)*0.5;;
            labelFrame.origin.y = imageFrame.origin.y+imageFrame.size.height+self.xlx_customSpace;
            self.titleLabel.frame = labelFrame;
            
            break;
        }
        case XLXButtonCustomStylePicLeft:
        {
            [self.titleLabel sizeToFit];
            CGRect labelFrame = self.titleLabel.frame;
            
            [self.imageView sizeToFit];
            CGRect imageFrame = self.imageView.frame;
            
            imageFrame.origin.x = (self.frame.size.width-imageFrame.size.width-labelFrame.size.width-self.xlx_customSpace)*0.5;
            imageFrame.origin.y = (self.frame.size.height-imageFrame.size.height)*0.5;
            self.imageView.frame = imageFrame;
            
            labelFrame.origin.x = imageFrame.origin.x+imageFrame.size.width+self.xlx_customSpace;
            labelFrame.origin.y = (self.frame.size.height-labelFrame.size.height)*0.5;
            self.titleLabel.frame = labelFrame;
            
            break;
        }
        case XLXButtonCustomStylePicDown:
        {
            [self.titleLabel sizeToFit];
            CGRect labelFrame = self.titleLabel.frame;
            
            [self.imageView sizeToFit];
            CGRect imageFrame = self.imageView.frame;
            
            labelFrame.origin.x = (self.frame.size.width-labelFrame.size.width)*0.5;
            labelFrame.origin.y = (self.frame.size.height-labelFrame.size.height-imageFrame.size.height-self.xlx_customSpace)*0.5;
            self.titleLabel.frame = labelFrame;
            
            imageFrame.origin.x = (self.frame.size.width-imageFrame.size.width)*0.5;
            imageFrame.origin.y = labelFrame.origin.y+labelFrame.size.height+self.xlx_customSpace;
            self.imageView.frame = imageFrame;
            
            break;
        }
        case XLXButtonCustomStylePicRight:
        {
            [self.titleLabel sizeToFit];
            CGRect labelFrame = self.titleLabel.frame;
            
            [self.imageView sizeToFit];
            CGRect imageFrame = self.imageView.frame;
            
            labelFrame.origin.x = (self.frame.size.width-imageFrame.size.width-labelFrame.size.width-self.xlx_customSpace)*0.5;
            labelFrame.origin.y = (self.frame.size.height-labelFrame.size.height)*0.5;
            self.titleLabel.frame = labelFrame;
            
            imageFrame.origin.x = labelFrame.origin.x+labelFrame.size.width+self.xlx_customSpace;
            imageFrame.origin.y = (self.frame.size.height-imageFrame.size.height)*0.5;
            self.imageView.frame = imageFrame;
            
            break;
        }
        default:
            break;
    }
}

@end

于是创建自定义的按钮就这么实现了

这里写图片描述

这样写的目的是什么呢?
还是一下几点,其仍然是一个按钮,原来按钮的任何属性方法都照旧,没有任何损害,
我们还可以使用button自带的富文本属性,来设置更高级点的效果
比如这样的


这里写图片描述

普通的写法或者xib我们可能会需要很多原生控件来实现
但是我们使用自定义的按钮只要简单的这样

懒加载


这里写图片描述

添加到视图(XIB就不描述了)


这里写图片描述

富文本内容修改


这里写图片描述

就这样 他们还是统一的按钮,仍然是系统的我们熟悉的UIButton.

试想一下,这样的按钮我们用着会不会很方便呢?

当然我们的自定义控件并不会就这么简单的结束了.
开发过程中我们会遇到很多特殊的需求,
比如要求我们写轮播图,我们有几种创建方式呢?
UIScrollView?UICollectionView?UIView?
这些我们都需要考虑
有的时候并不是我们简单的实现了需求就完事了,我们需要创建一个可复用的课迁移的耦合性底的控件才是目的.
项目中我们会遇到很多的自定义cell,会遇到特殊的动画,我们在自定义控件,或者写类别的时候我们都要考虑是否合适这么写,这样才能起到锻炼自己的能力.

另外,有很多的优秀的开源框架, 是如何做到耦合性低 是如何做到很好的于系统兼容,我们都应该学习,我们不应该单单的学习那些网络,异步等框架,那些框架固然需要学习,我们同事也该学习学习优秀的控件写法.
这有这样,我们才能够一步一步的实现自定义高级的控件乃至框架.

Demo地址:https://github.com/spicyShrimp/XLXButton

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

推荐阅读更多精彩内容

  • 重要:这是针对于正在开发中的API或技术的预备文档(预发布版本)。苹果提供这份文档的目的是帮助你按照文中描述的方式...
    金_波阅读 2,006评论 3 13
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 当我看着你远去的身影,当手中的花掉落一地,当天上的云朵分离,当大雁飞回时,当情字溢满心里,当你送我的礼物被藏起,当...
    穆炎阅读 378评论 1 4
  • lxp1055阅读 152评论 0 0