文档章节

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

Raindew
 Raindew
发布于 2016/07/26 12:31
字数 2406
阅读 34
收藏 1
点赞 1
评论 2

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

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

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

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

    分析完成,我们需要想着如何构造这个代码。底层是一个view,我们可以创建一个类继承UIView,然后在上面加控件。怎么做想好了,那我们需要公开哪些属性、方法供外界修改、使用呢?这里外观上:底部view背景色、滑块背景色、左右两个标题;事件上:左右滑动或者点击,滑块移动到指定位置后调用的方法。以上是我认为应该公开的属性、方法,当然如果你愿意可以公开字体,以及字体颜色,一切由你决定。

    好,一切就绪,我们开始封装的第一步:做一个入口。入口方式很多,有工厂方法、自定义实例方法、系统的init、initWithFrame方法...这里我们选用initWithFrame。为什么选这个?因为我们需要随意设定这个控件的位置。ok,我们开始写入口代码。

    创建一个类,继承UIView,然后到.m中写

- (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{

    _leftTitles = leftTitle;
    [self.leftButton setTitle:leftTitle forState:UIControlStateNormal];
    [self.thumbView setTitle:leftTitle forState:UIControlStateNormal];
}

- (void)setRightTitle:(NSString *)rightTitle{

    _rightTitles = rightTitle;
    [self.rightButton setTitle:rightTitle forState:UIControlStateNormal];
    [self.thumbView 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


@property (nonatomic,weak) id <YLSwitchDelegate> delegate;//代理

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

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

#pragma mark -- 左右两侧点击事件

- (void)leftButton:(UIButton *)button {

    //向左
    [self.thumbView setTitle:_leftTitles != nil ? _leftTitles : 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:_leftTitles != nil?_leftTitles:self.leftButton.titleLabel.text];

}

- (void)rightButton:(UIButton *)button {
    
    //向右
    [self.thumbView setTitle:_rightTitles != nil?_rightTitles: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:_rightTitles != nil?_rightTitles:self.rightButton.titleLabel.text];

}

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

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

#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:_rightTitles != nil?_rightTitles:self.rightButton.titleLabel.text forState:UIControlStateNormal];

                [self.delegate switchState:self rightTitle:_rightTitles != nil?_rightTitles: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:_leftTitles != nil?_leftTitles:self.leftButton.titleLabel.text forState:UIControlStateNormal];
                [self.delegate switchState:self leftTitle:_leftTitles != nil?_leftTitles: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,所以就不支持约束和旋转拉。

https://pan.baidu.com/s/1i5i6Ch3

© 著作权归作者所有

共有 人打赏支持
Raindew
粉丝 3
博文 16
码字总数 6727
作品 0
南京
iOS工程师
加载中

评论(2)

Raindew
Raindew

引用来自“HillYoung”的评论

不错啊,代码看上去很舒服
13不行啊,只会敲基础代码怎么办
H
HillYoung
不错啊,代码看上去很舒服
Python面向对象编程之我见

面向对象基本概念 面向对象是一种编程范式。范式是指一组方法论。编程范式是一组如何组织代码的方法论。编程范式指的是软件工程中的一种方法学。 一些主流的编程范式: OOP - 面向对象编程 ...

bigstone2012 ⋅ 05/31 ⋅ 0

微软正式推出自有社交网站 Socl

自去年年底就开始秘密测试、今年5月又有改版的Socl终于开始面向大众开始公测了。 Socl 最初只向微软内部员工和一些大学的学生开放,采取的是基于兴趣的社交网络路线。它融合了搜索和社交,从...

oschina ⋅ 2012/12/06 ⋅ 14

iOS开发之将XML转换成树

开发中由于服务端与客户端是两种不同的平台,而且服务端又是老系统,不具备很好的面向对象的性质,所以导致客户端与服务端只好通过一些制定好的xml进行通信。 在iOS中对XML的解析不像donet这...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

Joda-Time简介

Joda-Time是一个使用java开发的强大易用的日期和时间库。易于使用是 Joda 的主要设计目标。其他目标包括可扩展性、完整的特性集以及对多种日历系统的支持。并且 Joda 与 JDK 是百分之百可互操...

孟飞阳 ⋅ 2016/10/29 ⋅ 0

Web 使用fetch请求后端服务

上篇我们使用了ssm构建了我们简历的服务, 这篇我们从前端的视角进行网络请求, 对于react就要讲讲fetch这个库的使用, 对于vue就要讲讲axios这个库, 对于axios之前已经讲过, 这篇就来讲讲fetch...

Castie1 ⋅ 2017/11/15 ⋅ 0

【转】常见Java面试题

常见Java面试题   问题:如果main方法被声明为private会怎样?   答案:能正常编译,但运行的时候会提示"main方法不是public的"。   问题:Java里的传引用和传值的区别是什么?   答...

一只死笨死笨的猪 ⋅ 2014/09/30 ⋅ 0

hibernate 级联查询 过滤属性 使用hibernateTemplate

灯光下的宁静 ⋅ 2014/05/20 ⋅ 0

java基础面试题2

1:如果main方法被声明为private会怎样? 能正常编译,但运行的时候会提示”main方法不是public的”。 2:Java里的传引用和传值的区别是什么? 传引用是指传递的是地址而不是值本身,传值则是...

Carbenson ⋅ 2015/09/16 ⋅ 0

IOS用sqlite的原生代码操作数据库详解 总和两个网页的讲解

ios Sqlite数据库增删改查基本操作 第一个讲解网页的内容 研究了几天的数据库,终于把它给搞出来了。Sqlite是ios上最常用的数据库之一,大家还是有必要了解一下的。这是仿照网上的一个例子做...

HillYoung ⋅ 2014/02/14 ⋅ 0

class类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据...

327051661 ⋅ 2017/11/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

js模拟栈和队列

栈和队列 栈:LIFO(先进后出)一种数据结构 队列:LILO(先进先出)一种数据结构 使用的js方法 1.push();可以接收任意数量的参数,把它们逐个推进队尾(数组末尾),并返回修改后的数组长度。 2....

LIAOJIN1 ⋅ 22分钟前 ⋅ 0

180619-Yaml文件语法及读写小结

Yaml文件小结 Yaml文件有自己独立的语法,常用作配置文件使用,相比较于xml和json而言,减少很多不必要的标签或者括号,阅读也更加清晰简单;本篇主要介绍下YAML文件的基本语法,以及如何在J...

小灰灰Blog ⋅ 30分钟前 ⋅ 0

IEC60870-5-104规约传送原因

1:周期循环2:背景扫描3:自发4:初始化5:请求6:激活7:激活确认8:停止激活9:停止激活确认10:激活结束11:远程命令引起的返送信息12:当地命令引起的返送信息13:文件传送20:响应总召...

始终初心 ⋅ 43分钟前 ⋅ 0

【图文经典版】冒泡排序

1、可视化排序过程 对{ 6, 5, 3, 1, 8, 7, 2, 4 }进行冒泡排序的可视化动态过程如下 2、代码实现    public void contextLoads() {// 冒泡排序int[] a = { 6, 5, 3, 1, 8, 7, 2, ...

pocher ⋅ 54分钟前 ⋅ 0

ORA-12537 TNS-12560 TNS-00530 ora-609解决

oracle 11g不能连接,卡住,ORA-12537 TNS-12560 TNS-00530 TNS-12502 tns-12505 ora-609 Windows Error: 54: Unknown error 解决方案。 今天折腾了一下午,为了查这个问题。。找了N多方案,...

lanybass ⋅ 今天 ⋅ 0

IDEA反向映射Mybatis

1.首先在pom文件的plugins中添加maven对mybatis-generator插件的支持 ` <!-- mybatis逆向工程 --><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-ma......

lichengyou20 ⋅ 今天 ⋅ 0

4.10/4.11/4.12 lvm讲解 4.13 磁盘故障小案例

准备磁盘分区 fdisk /dev/sdb n 创建三个新分区,分别1G t 改变分区类型为8e 准备物理卷 pvcreate /dev/sdb1 pvcreate /dev/sdb2 pvcreate /dev/sdb3 pvdisplay/pvs 列出当前的物理卷 pvremo...

Linux_老吴 ⋅ 今天 ⋅ 0

zabbix 3.4安装

#已装好lamp环境 1.安装相关yum仓库 rpm -i http://repo.zabbix.com/zabbix/3.4/rhel/7/x86_64/zabbix-release-3.4-2.el7.noarch.rpm #tip:rpm -ql zabbix-release 看上面这个软件装了哪些东......

山月关 ⋅ 今天 ⋅ 0

Java的Excel导出工具类

首先在POM中引入需要的Jar <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency><dependency><groupId>o......

Kxvz ⋅ 今天 ⋅ 0

springboot 使用jsp

目录结构: 启动文件的Application必须在contorller文件的父级 文件路径在src/main/webapp下面 我的配置:前缀是/WEB-INF/jsp/ pom.xml需要加入tomcat-embed-jasper, 对jsp的支持的依赖 <de...

夜醒者 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部