美文网首页111
系列:iOS开发-从扩展UIButton到自定义控件

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

作者: spicyShrimp | 来源:发表于2017-09-20 15:35 被阅读16次

    系列: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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/ztsosxtx.html