美文网首页iOS FoundationsiOS Developer封装继承多态
iOS初来乍到,你如何开始第一个封装类?

iOS初来乍到,你如何开始第一个封装类?

作者: Raindew | 来源:发表于2017-03-07 10:04 被阅读632次

    此博客为初级iOS而写,这里不讲难以理解的知识,就说说怎么样封装一个简单的控件。我记得在我开始自己第一个封装控件的时候,真的是一筹莫展,就像抱着一个西瓜不知道怎么下口。

    我们经常用到三方控件,无一例外都是封装好的。要想快速学习,有一个途径就是看别人的封装源码。封装好才类使用起来很爽,很傻瓜易用。在此感谢那些无私开源的人,给我们很多学习的机会。网上封装多不胜数,但却很少有人告诉一个小白你应该怎么去封装。即便是初级我想你可能也封装过自己的类,我也如此,只不过我愿意把这些写出来给不会的人看看。

    学习编程的时候听老师讲过OC的三大特性,其中一个就是封装。封装博大精深,也许用我们初级人的理解大概就是将重复用到的功能控件包装起来,既把代码模块化。尽管目光短浅,但这个出发点是对的,可喜的。说到这不得不提“面向对象”这个术语了,简单理解封装好的模块就是一个对象,你使用这个模块就在面向对象编程。面向对象,是一种编程思想。

    下面我以一个小控件为例,在封装的过程中逐步讲解。我们先看一下UI设计图。



    分析:这是一个拉长了的switch控件,又像一个分段控制器。它有左右两个按钮,中间的滑块,底层的一个view,我们猜想它肯定是可以滑动,并且点击的。那么,自然当滑动滑块或者点击左右按钮后,滑块应该是左右移动的,移动完成可能需要调用一个方法来做别的事情,移动的过程中可能还需要一个动画。

    分析完成,我们需要想着如何构造这个代码。底层是一个view,我们可以创建一个类继承UIView,然后在上面加控件。怎么做想好了,那我们需要公开哪些属性、方法供外界修改、使用呢?这里外观上:底部view背景色、滑块背景色、左右两个标题;事件上:左右滑动或者点击,滑块移动到指定位置后调用的方法。以上是我认为应该公开的属性、方法,当然如果你愿意可以公开字体,以及字体颜色,一切由你决定。
    好,一切就绪,我们开始封装的第一步:做一个入口。入口方式很多,有工厂方法、自定义实例方法、系统的init、initWithFrame方法...这里我们选用initWithFrame。为什么选这个?因为我们需要随意设定这个控件的位置。ok,我们开始写入口代码。
    创建一个类,继承UIView,然后到.m中写

        #pragma mark -- init
    
    - (instancetype)initWithFrame:(CGRect)frame {
    
        self = [super initWithFrame:frame];
        if (self) {
            self.frame = frame;
            [self setLeftAndRightButton];
            self.backgroundColor = [UIColor colorWithRed:243/255. green:143/255. blue:179/255. alpha:1.];
            self.layer.cornerRadius = CGRectGetHeight(self.frame) / 2.;
        }
        return self;
    }
    

    记得这里是父类的方法,请用父类初始化。然后我们调用了一个配置左右按钮的方法,并且设置了self(self就是底层的view)的背景颜色,和圆角。下面我们主要构造setLeftAndRightButton这个方法就可以了。

    
    - (void)setLeftAndRightButton {
    
        self.leftButton = [UIButton buttonWithType:UIButtonTypeSystem];
    
        self.leftButton.frame = CGRectMake(0, 1, CGRectGetWidth(self.frame) / 2., CGRectGetHeight(self.frame) - 2);
    
        self.rightButton = [UIButton buttonWithType:UIButtonTypeSystem];
        
        self.rightButton.frame = CGRectMake(CGRectGetWidth(self.frame) / 2., 1, CGRectGetWidth(self.frame) / 2., CGRectGetHeight(self.frame) - 2);
    
        [self.leftButton addTarget:self action:@selector(leftButton:) forControlEvents:UIControlEventTouchUpInside];
    
        [self.rightButton addTarget:self action:@selector(rightButton:) forControlEvents:UIControlEventTouchUpInside];
    
    
        [self addSubview:self.leftButton];
        [self addSubview:self.rightButton];
    
        self.thumbView = [UIButton buttonWithType:UIButtonTypeSystem];
        self.thumbView.frame = CGRectMake(1, 1, CGRectGetWidth(self.frame) / 2. - 1, CGRectGetHeight(self.frame) - 2);
        self.thumbView.layer.cornerRadius = CGRectGetHeight(self.thumbView.frame) / 2.;
        self.thumbView.backgroundColor = [UIColor whiteColor];
    
        //取消闪烁
        self.thumbView.adjustsImageWhenHighlighted = NO;
        self.rightButton.adjustsImageWhenHighlighted = NO;
        self.leftButton.adjustsImageWhenHighlighted = NO;
    
        [self.rightButton setTitle:@"second" forState:UIControlStateNormal];
        [self.leftButton setTitle:@"frist" forState:UIControlStateNormal];
        [self.thumbView setTitle:@"frist" forState:UIControlStateNormal];
    
        //字体颜色
        UIColor *blackColor = [UIColor blackColor];
        [self.thumbView setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
        [self.leftButton setTitleColor:blackColor forState:UIControlStateNormal];
        [self.rightButton setTitleColor:blackColor forState:UIControlStateNormal];
    
    
        [self addSubview:self.thumbView];
    
        //拖动手势
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(change:)];
        [self.thumbView addGestureRecognizer:panGesture];
    }
    

    这个方法中可以看到,我们创建了左右两个button和一个滑块button,然后进行位置计算,并且给左右两个按钮加上点击事件,并且,我们设置了默认标题,处理了闪烁,加了美观的圆角。在最后我们加了一个滑动手势事件。我们先来看看点击事件,点击事件中我们需要处理什么呢?1.滑块移动 2.公开点击事件 由于滑动后的事件也需要公开,那么我们就统一放一起吧,这里先处理点击时滑块移动。
    哦,这里恐怕不得不先说说这个公开的属性问题了。因为滑动的话滑块上面的标题要改变,那么索性我们先把开始想好的属性公开吧。到.h中

    /**
     *  公开属性
     */
    @property (nonatomic,strong) NSString *rightTitle;//右侧标题    默认为 second
    
    @property (nonatomic,strong) NSString *leftTitle;//左侧标题     默认为 frist
    
    @property (nonatomic,strong) UIColor *bgColor;//背景颜色        默认为 粉色
    
    @property (nonatomic,strong) UIColor *thumbColor;//滑块颜色     默认为 白色
    

    好了属性既然公开了,如何用呢?当然是set方法了。这里在.m中直接重写属性的set方法,要说的是,千万不要手敲,直接敲-set...后面直接回车出来吧,避免写错单词!(这点很重要)。我们来看看.m

    
    #pragma mark -- set/get...
    
    - (void)setLeftTitle:(NSString *)leftTitle{
    
        [self.leftButton setTitle:leftTitle forState:UIControlStateNormal];
        [self.thumbView setTitle:leftTitle forState:UIControlStateNormal];
    }
    
    - (void)setRightTitle:(NSString *)rightTitle{
    
        [self.rightButton setTitle:rightTitle forState:UIControlStateNormal];
    
    }
    
    - (void)setBgColor:(UIColor *)bgColor {
    
        self.backgroundColor = bgColor;
    }
    
    - (void)setThumbColor:(UIColor *)thumbColor {
    
        self.thumbView.backgroundColor = thumbColor;
    
    }
    

    可以看到我们在set方法里面做了一些事情,我们把当外部class.属性的时候,把这个值赋给.m的另一个相同类型的属性。例如

       _leftTitles = leftTitle;     
    

    现在我们公开另一个重要的东西:滑块移动到指定位置后的响应事件。block、代理、通知...这里我们选用苹果最常用的代理模式。来看.h

    @protocol YLSwitchDelegate <NSObject>
    
    @optional
    
    /*
    * 切换到左侧
     */
    -(void)switchState:(UIView *)view leftTitle:(NSString *)title;
    /*
     * 切换到右侧
     */
    -(void)switchState:(UIView *)view rightTitle:(NSString *)title;
    
    @end
    
    

    声明你的代理。多说一句不要误写成 strong

    
    @property (nonatomic,weak) id <YLSwitchDelegate> delegate;//切换代理
    
    //注:  多个YLSwitch代理方法请使用tag进行调用区分
    
    

    如上代码,不细说了。我们只需要在点击后和滑动后调用这个代理即可。

    好了,我们接着做点击事件的滑块移动吧!

    #pragma mark -- 左右两侧点击事件
    
    - (void)leftButton:(UIButton *)button {
    
        //向左
        [self.thumbView setTitle:self.leftTitle != nil ? self.leftTitle : self.leftButton.titleLabel.text forState:UIControlStateNormal];
        [UIView animateWithDuration:0.3 animations:^{
            self.thumbView.frame = CGRectMake(1, 1, CGRectGetWidth(self.frame) / 2. - 1, CGRectGetHeight(self.frame) - 2);
    
        } completion:^(BOOL finished) {
    
        }];
        [self.delegate switchState:self leftTitle:self.leftTitle != nil?self.leftTitle:self.leftButton.titleLabel.text];
    
    }
    
    - (void)rightButton:(UIButton *)button {
        
        //向右
        [self.thumbView setTitle:self.rightTitle != nil?self.rightTitle:self.rightButton.titleLabel.text forState:UIControlStateNormal];
        [UIView animateWithDuration:0.3 animations:^{
            self.thumbView.frame = CGRectMake(CGRectGetWidth(self.frame) / 2. - 1, 1, CGRectGetWidth(self.frame) / 2. - 1, CGRectGetHeight(self.frame) - 2);
    
        } completion:^(BOOL finished) {
    
        }];
    
        [self.delegate switchState:self rightTitle:self.rightTitle != nil?self.rightTitle:self.rightButton.titleLabel.text];
    
    }
    
    

    两个方法里面我们在一个动画里面改变了thumbView(滑块)的标题、位置,设置一个动画时间。特别注意,我们在最后调用了代理。(为了简单我用三目运算)如果你喜欢阻尼、弹跳效果,可以自行参考UIView动画。

    不要忘记我们还有一个拖动手势事件没有处理。

    多嘴一句,任何一个代码块要有写上mark的习惯

    #pragma mark -- PanGestureRecognizerEvent
    
    - (void)change:(UIPanGestureRecognizer *)sender {
    
        CGPoint point = [sender translationInView:self.thumbView];
    
         //向右
        if (point.x > 0) {
    
            //不能超过范围
            if (point.x > CGRectGetWidth(self.thumbView.frame)) {
                point.x = CGRectGetWidth(self.thumbView.frame);
            }
            self.thumbView.frame = CGRectMake(point.x, 1, CGRectGetWidth(self.frame) / 2. - 1, CGRectGetHeight(self.frame) - 2);
    
    
            //停止了拖动
            if ([sender state] == UIGestureRecognizerStateEnded) {
    
    
                if (point.x >= CGRectGetWidth(self.thumbView.frame) / 2.) {
    
                    self.thumbView.frame = CGRectMake(CGRectGetWidth(self.frame) / 2. - 1, 1, CGRectGetWidth(self.frame) / 2. - 1, CGRectGetHeight(self.frame) - 2);
                    [self.thumbView setTitle:self.rightTitle != nil?self.rightTitle:self.rightButton.titleLabel.text forState:UIControlStateNormal];
                    [self.delegate switchState:self rightTitle:self.rightTitle != nil?self.rightTitle:self.rightButton.titleLabel.text];
    
                }else {
                    self.thumbView.frame = CGRectMake(1, 1, CGRectGetWidth(self.frame) / 2. - 1,CGRectGetHeight(self.frame) - 2.);
                }
            }
    
        }else {
    
            //向左
            if (point.x < -CGRectGetWidth(self.thumbView.frame)) {
                point.x = -CGRectGetWidth(self.thumbView.frame);
            }
            self.thumbView.frame = CGRectMake(CGRectGetWidth(self.frame) / 2. + point.x - 1, 1, CGRectGetWidth(self.frame) / 2. - 1, CGRectGetHeight(self.frame) - 2.);
    
            //停止了拖动
    
            if ([sender state] == UIGestureRecognizerStateEnded) {
    
                if (point.x <= -CGRectGetWidth(self.thumbView.frame) / 2.) {
    
                    self.thumbView.frame = CGRectMake(1, 1, CGRectGetWidth(self.frame) / 2. - 1,CGRectGetHeight(self.frame) - 2);
                    [self.thumbView setTitle:self.leftTitle != nil?self.leftTitle:self.leftButton.titleLabel.text forState:UIControlStateNormal];
                    [self.delegate switchState:self leftTitle:self.leftTitle != nil?self.leftTitle:self.leftButton.titleLabel.text];
                }else {
    
                    self.thumbView.frame = CGRectMake(CGRectGetWidth(self.frame) / 2. - 1, 1, CGRectGetWidth(self.frame) / 2. - 1, CGRectGetHeight(self.frame) - 2);
    
                }
            }
        }
    }
    
    

    以上计算可能有点多。我大致说下:最开始我们限制了拖动的距离,上面的 1 呢是为了留滑块与底层的间隙,我们获取停止拖动的时机,进行一个很重要的判断,当拖动距离大于一半的时候我们就让滑块滑动到最终位置,反之则回到最初位置(这里其实是一个用户体验问题,你完全可以不写,但是我认为那可能并不优雅)。最后我们在左右停止拖动的时候分别调用了我们的代理。这样就能够保证外部在拖动和点击后都可以获取这个时机,去做其他的事情。下面我贴出调用代码。

    #import "ViewController.h"
    #import "YLSwitch.h"
    @interface ViewController ()<YLSwitchDelegate>
    @end
    @implementation ViewController
    - (void)viewDidLoad {
         [super viewDidLoad]; 
         YLSwitch *mySwitch = [[YLSwitch alloc] initWithFrame:CGRectMake(0, 0, 210, 35)];
         mySwitch.tag = 1;
         mySwitch.delegate = self;
         self.navigationItem.titleView = mySwitch;
         YLSwitch *mySwitch2 = [[YLSwitch alloc] initWithFrame:CGRectMake(100, 200, 210, 35)]; mySwitch2.tag = 2; 
         mySwitch2.delegate = self;
         mySwitch2.leftTitle = @"左"; mySwitch2.rightTitle = @"右"; mySwitch2.bgColor = [UIColor redColor]; mySwitch2.thumbColor = [UIColor purpleColor]; [self.view addSubview:mySwitch2];
    }
    #pragma mark -- YLSwitchDelegate
    - (void)switchState:(UIView *)view leftTitle:(NSString *)title { 
        if (view.tag == 1) { 
           NSLog(@"导航栏switch");
         }
         NSLog(@"%@",title);
    }
    - (void)switchState:(UIView *)view rightTitle:(NSString *)title { 
        if (view.tag == 1) { 
           NSLog(@"导航栏switch");
     } 
        NSLog(@"%@",title); 
    }
    

    下面是效果图。



    到这里就完成了一个简单控件的封装。附上demo。其实东西特别简单,只是希望给刚接触iOS编程的人看看吧,毕竟我刚开始时候没有人告诉我这样去做。
    注:这个博客只要想表达如何开始封装,因此没有做layerSubViews方法,以及awakeNib,所以就不支持约束和旋转拉。

    总结:一个好的封装控件要做到高内聚、低耦合。具体怎样做可能需要你以长久的时光付诸实践并且总结经验。一个简单的控件我啰啰嗦嗦写了很多,为什么这样做,其实一切都是为了一个新手也能读懂,我深知两年前自己的困惑。

    此文若能对新手有一丝引荐之助,必不枉我口水滔滔。
    iOS技术交流群:511860085 欢迎加入!
    代码就不存git了,直接拖进百度云盘了。如果连接失效留言即可
    代码传送门

    相关文章

      网友评论

      本文标题:iOS初来乍到,你如何开始第一个封装类?

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