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

此博客为初级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,所以就不支持约束和旋转拉。

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

此文若能对新手有一丝引荐之助,必不枉我口水滔滔。 代码就不存git了,直接拖进百度云盘了。如果连接失效留言即可 代码传送门

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

2657
来自专栏张善友的专栏

Mix 10 上的asp.net mvc 2的相关Session

Beyond File | New Company: From Cheesy Sample to Social Platform Scott Hansel...

2517
来自专栏芋道源码1024

熔断器 Hystrix 源码解析 —— 断路器 HystrixCircuitBreaker

本文主要基于 Hystrix 1.5.X 版本 1. 概述 2. HystrixCircuitBreaker 3. HystrixCircuitBreaker....

5237
来自专栏hbbliyong

WPF Trigger for IsSelected in a DataTemplate for ListBox items

<DataTemplate DataType="{x:Type vm:HeaderSlugViewModel}"> <vw:HeaderSlug...

4044
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3035
来自专栏一个会写诗的程序员的博客

Spring Reactor 项目核心库Reactor Core

Non-Blocking Reactive Streams Foundation for the JVM both implementing a Reactiv...

2102
来自专栏闻道于事

js登录滑动验证,不滑动无法登陆

js的判断这里是根据滑块的位置进行判断,应该是用一个flag判断 <%@ page language="java" contentType="text/html...

6508
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

3945
来自专栏菩提树下的杨过

Flash/Flex学习笔记(23):运动学原理

先写一个公用的小球类Ball: package{ import flash.display.Sprite; //小球 类 public class B...

25210
来自专栏飞扬的花生

jsencrypt参数前端加密c#解密

      写程序时一般是通过form表单或者ajax方式将参数提交到服务器进行验证,如何防止提交的请求不被抓包后串改,虽然无法说绝对安全却给非法提交提高了难度...

3849

扫码关注云+社区