您的位置:首页 > 移动开发 > IOS开发

iOS开发:代码通用性以及其规范 第二篇(猜想iOS中实现TableView内部设计思路(附代码),以类似的思想实现一个通用的进度条)

2015-08-29 20:12 1236 查看
在iOS开发中,经常是要用到UITableView的,我曾经思考过这样一个问题,为什么任何种类的model放到TableView和所需的cell里面,都可以正常显示?而我自己写的很多view却只是能放一种特定的model,就好像我这个view是专门为了展示这个model所设计的?有没有一种设计方法,使得我所设计的一些view也可以放任何合适种类的model,并且按照预期的那样正确展示呢?

(前一篇记录了个人理解的OC开发中代码规范以及代码通用性,这一篇主要是设计一个通用的进度条。)

为了解决这个问题,我特地去看了iOS开发中的一些设计模式,也研究过别人写的一些框架以及苹果给出的UITableView的方法,发现最主要的原因在于UITableViewDataSource和UITableViewDelegate上。

在这里,得说说我对iOS开发中数据源协议(dataSource)和代理协议(delegate)的理解,个人认为主要是为遵守这些个协议的类新增一些方法,而这些方法用来与拥有dataSource\delegate的那个类进行通信(也可以说是处理事件、传递数据等)。比如说,TableView中的dataSource协议中必须要实现下面这三个方法:

#pragma mark - Table view 数据源方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;


其中这两个方法,是tableView在向遵守UITableViewDataSource协议的类拿它所需要的数据,比如说,它将要展示多少组数据?每组数据多少行?

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

至于它内部的实现,我猜想应该是这么实现的:

NSInteger totalSection = [self.dataSource numberOfSectionsInTableView:self];

int rowsOfSection[totalSection];

for (int i = 0; i < totalSection; i++) {
rowsOfSection[i] = [self.dataSource tableView:self numberOfRowsInSection:i];
}


上面的self代指tableView自己本身。内部通过这样类似的方法,就可以拿到所要展示的组数以及每组展示的行数了(苹果具体如何实现我并不知道,但这不妨碍我猜想,按照其设计的思想猜想其内部如何实现)。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;


这个方法,在tableView里面是很有地位的。我猜想,苹果代码可能是通过dataSource调用这个方法,拿到cell(也就是一个view)然后去展示,也不仅仅是展示这么简单,还需要做重复利用这一操作,也就是缓存池的实现。如果让我来实现,我会在拿到对应位置的cell时,通过delegate里面的

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

得到cell的高度,设计好其在scrollView上的frame,然后在scrollView滚动的时候,拿到它的contentOffset(事实上,这是UIScrollView的属性,不过,UITableView继承自UIScrollView,所要也具有此属性),然后判断哪些cell需要显示在屏幕上,哪些cell不需要显示在屏幕上,需要显示在屏幕上得,先判断其是否正显示在屏幕上,如果没有,先从缓存池中找是否存在相同identifie的cell,不存在的话,那么就返回nil......当然,如果要实现一个缓存池,也是做得到的,但是在这一篇,并不打算详细写,具体可以查看以后的第四篇,关于一个瀑布流的实现

以上,是我猜想官方实现tableView的过程,事实上,UIKit框架不开源,我并不知道其内部如何实现,但是,按照我这种猜想的思路,我是可以实现一个UITableView的(也许,效率上会不如官方的)

基于此,我可以确定了,为什么UITableView可以展示不同种类的model数据?主要就是,它并不持有model,甚至可以理解为,UITableView内部并未有model,所以也就不存在只能展示某一种model数据了。

为什么会如此:
可以仔细看看,它是怎么得到数据的?是通过它的dataSource,不断的像遵守dataSource的类(控制器)问数据!
喂喂,控制器,你没告诉我,这个表格有多少组啊:
喂,控制器,你赶紧把每组多少行告诉我.....
控制器,你丫的不告诉我cell(view),我怎么知道如何展示它啊?

基于这样的思路,我在自己设计控件的时候,完全也可以是搞出一个dataSource协议,不断的向那些遵守了dataSource协议的家伙,要人要钱,不给?!信不信我分分钟崩溃一个给你看......
delegate也是可以参考上面思路实现的,不过它与dataSource所不同的是,dataSource所索要的是data(也就是数据),而delegate所需要是一些处理功能性上的方法,比如说UITableView中某个cell被点击了(选中了),那么这是事件处理,放在delegate里面比较合适,就比如我们总不能硬是将一种狗叫(说)成是一只猫......这个问题,在代码规范层面上看来,是很严肃的。

下面正式进入一个通用进度条的代码实现,用上面提到的方法实现,先来看看效果图:
只有四个进度:





有6个进度:





直接从码农调到了CTO,中间两个无效:





//如果引用我写个这个框架,以上只需要在控制器里面的代码:

#import "ViewController.h"
#import "ZYProgressView.h"

@interface ViewController () <ZYProgressViewDataSource, ZYProgressViewDelegate>
@property (nonatomic, weak) ZYProgressView *progressView;
@property (nonatomic, strong) NSArray *titles;
@end

@implementation ViewController

- (NSArray *)titles
{
if (_titles == nil) {
_titles = @[@"菜鸟", @"码农", @"高级工程师", @"项目经理", @"CTO", @"迎娶白富美"];
}
return _titles;
}
- (void)viewDidLoad {
[super viewDidLoad];

ZYProgressView *progressView = [[ZYProgressView alloc] init];
progressView.frame = CGRectMake(0, 100, self.view.frame.size.width, 170);
progressView.dataSource = self;
progressView.delegate = self;
//纯代码实现,这个方法可调,可不调。但是如果是通过xib创建,必须要调用此方法
[progressView reloadData];
self.progressView = progressView;
[self.view addSubview:progressView];

//显示到当前进度,从1开始
self.progressView.currentProgress = 6;

//中间跳过两个状态,注意,存放的值,要从1开始
self.progressView.items = @[@(3),@(4)];
}

#pragma mark ----ZYProgressViewDataSource

//告诉progressView,总共要显示多少个进度
- (NSUInteger)numberOfProgressInProgressView
{
return self.titles.count;
}

//告诉progressView,每个进度的title,索引从0开始
- (NSString *)progressView:(ZYProgressView *)progressView titleAtIndex:(NSUInteger)index
{
return self.titles[index];
}

@end


其他的,我也提供了实现各种不同颜色的方法,只需要遵守delegate协议,实现相应方法即可,比如说高亮的时候,为黄色:





//只需要在控制器里面加上如下代码:

#pragma mark ----ZYProgressViewDelegate
- (UIColor *)highlightColorForCircleViewInProgressView:(ZYProgressView *)progressView
{
return [UIColor yellowColor];
}


其他更多特色的颜色、字体、间距等,.h文件里面有详细介绍,这里主要说说我实现的思路:

先附上代码:

//  用法与UITableView相当,需要遵守ZYProgressViewDataSource,ZYProgressViewDelegate
//  其中ZYProgressViewDataSource里面的方法,是必须实现的
//  ZYProgressViewDelegate里面的方法为可选择的(有待完善)

#import <UIKit/UIKit.h>

@class ZYProgressView;

@protocol ZYProgressViewDataSource <NSObject>

/**
*  进度数目
*
*/
- (NSUInteger)numberOfProgressInProgressView;

/**
*  每个进度对应的标题
*
*  @param progressView
*  @param index        在index下对应的标题(index从0开始)
*
*  @return 标题
*/
- (NSString *)progressView:(ZYProgressView *)progressView titleAtIndex:(NSUInteger)index;

@end

@protocol ZYProgressViewDelegate <NSObject>
@optional
/**
*  圆的normal颜色(默认normal颜色为灰色)
*
*
*/
- (UIColor *)colorForCircleViewInProgressView:(ZYProgressView *)progressView;

/**
*  圆的highlight颜色(默认为红色)
*
*/
- (UIColor *)highlightColorForCircleViewInProgressView:(ZYProgressView *)progressView;

/**
*  标题的normal颜色(默认normal颜色为灰色)
*
*
*/
- (UIColor *)colorForTitleViewInProgressView:(ZYProgressView *)progressView;

/**
*  标题的hightlight颜色(默认颜色为红色)
*
*/
- (UIColor *)highlightColorForTitleViewInProgressView:(ZYProgressView *)progressView;

/**
*  设置圆的半径,默认为10
*
*  @param progressView
*
*/
- (CGFloat)radiusForCircleViewInProgressView:(ZYProgressView *)progressView;

/**
*  设置标题的字体,默认为11
*
*  @param progressView
*
*/
- (UIFont *)fontForTitleViewInProgressView:(ZYProgressView *)progressView;

@end

@interface ZYProgressView : UIView
@property (nonatomic, weak) id<ZYProgressViewDataSource>dataSource;
@property (nonatomic, weak) id<ZYProgressViewDelegate>delegate;

/**
*  处理任务已经到了第n阶段,但是中间第n-4,n-5等阶段未完成的情况
*
*  items 数组,如果是第n-4,n-5阶段未完成,那么数组中存放@(n-4),@(n-5)  注意,存放的值,要从1开始
*/
@property (nonatomic, strong) NSArray *items;

/**
*  当前进度,可显示高亮颜色,进度值应当从1开始
*/
@property (nonatomic, assign) int currentProgress;

/**
*  刷新数据,当需要动态添加一个进度时,可重新刷新数据
*  如果是直接是在xib/storyboard里面创建,那么创建之后,在设置好dataSource和delegate之后,请马上调用此方法刷新数据
*/
- (void)reloadData;
@end


#import "ZYProgressView.h"

@interface ZYProgressView ()
@property (nonatomic, strong) NSMutableArray *circles;
@property (nonatomic, strong) NSMutableArray *lines;
@property (nonatomic, strong) NSMutableArray *titles;
@end

#define DefaultRadius  10
#define DefaultFont  [UIFont systemFontOfSize:11.0]
#define DefaultCircleColor [UIColor colorWithRed:218 / 255.0 green:208 / 255.0 blue:209 / 255.0 alpha:1];
#define DefaultTitleColor [UIColor colorWithRed:218 / 255.0 green:208 / 255.0 blue:209 / 255.0 alpha:1];
#define DefaultHighCircleColor [UIColor colorWithRed:251.0 / 255.0 green:0 blue:52.0 / 255.0 alpha:1];
#define DefaultHighTitleColor [UIColor colorWithRed:102.0 / 255.0 green:102.0 / 255.0 blue:102.0 / 255.0 alpha:1];
@implementation ZYProgressView

- (NSMutableArray *)circles
{
if (!_circles) {
_circles = [NSMutableArray array];
}
return _circles;
}

- (NSMutableArray *)lines
{
if (!_lines) {
_lines = [NSMutableArray array];
}
return _lines;
}

- (NSMutableArray *)titles
{
if (!_titles) {
_titles = [NSMutableArray array];
}
return _titles;
}

- (void)setCurrentProgress:(int)currentProgress
{
int numberOfProgress = (int)[self.dataSource numberOfProgressInProgressView];
_currentProgress = currentProgress;
if (_currentProgress > numberOfProgress) {
NSLog(@"Error: ZYProgressView中的currentProgress > numberOfProgress");
return;
}
[self statusViewForCurrentProgress:_currentProgress];

if (_items && _items.count > 0) {
[self setItems:_items];
}
}

- (void)setItems:(NSArray *)items
{
_items = items;

for (NSNumber *obj in items) {
int number = obj.intValue - 1;
UILabel *label = self.titles[number];
label.textColor = [self titleNormalColor];

UIView *circleView = self.circles[number];
circleView.backgroundColor = [self circleNormalColor];
}
}

- (void)reloadData
{
[self.circles makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.circles removeAllObjects];
[self.lines makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.lines removeAllObjects];
[self.titles makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.titles removeAllObjects];
int numberOfProgress = (int)[self.dataSource numberOfProgressInProgressView];
if (numberOfProgress == 0) return;

for (int i = 0; i < numberOfProgress; i++) {
NSString *title = [self.dataSource progressView:self titleAtIndex:i];
UILabel *label = [self labelWithTitle:title];
[self.titles addObject:label];
[self addSubview:label];

UIView *circleView = [[UIView alloc] init];
[self.circles addObject:circleView];
[self addSubview:circleView];

if (i != 0) {
UIView *lineView = [[UIView alloc] init];
[self.lines addObject:lineView];
[self addSubview:lineView];
}
}
}

- (void)layoutSubviews
{
[super layoutSubviews];

int numberOfProgress = (int)[self.dataSource numberOfProgressInProgressView];
if (numberOfProgress == 0) return;

CGFloat marginLeft = 15;
CGFloat marginRight = 15;
CGFloat marginTop = 25;
CGFloat marginRow = 12;
CGFloat radiusOfCircle = [self radiusForCircle];
CGFloat lineHeight = 2;
CGFloat lineWidth = (self.frame.size.width - numberOfProgress * radiusOfCircle - marginLeft - marginRight ) / ((double)numberOfProgress - 1.0) + 0.1;
CGFloat circleViewX = marginLeft;

for (int i = 0; i < numberOfProgress; i++) {
UIView *circleView = self.circles[i];

circleView.frame = CGRectMake(circleViewX, marginTop, radiusOfCircle, radiusOfCircle);
[self circleViewWithView:circleView];

UILabel *label = self.titles[i];
if (i == 0) {
label.frame = CGRectMake(circleViewX, CGRectGetMaxY(circleView.frame) + marginRow, 0, 0);
[label sizeToFit];
}
else if (i != numberOfProgress - 1)
{
[label sizeToFit];
label.center = CGPointMake(circleView.center.x, 0);
label.frame = CGRectMake(label.frame.origin.x, CGRectGetMaxY(circleView.frame) + marginRow, label.frame.size.width, label.frame.size.height);
}
else
{
[label sizeToFit];
label.frame = CGRectMake(CGRectGetMaxX(circleView.frame) - label.frame.size.width, CGRectGetMaxY(circleView.frame) + marginRow, label.frame.size.width, label.frame.size.height);
}

if (i != 0) {
UIView *lineView = self.lines[i - 1];
lineView.frame = CGRectMake(CGRectGetMaxX([self.circles[i - 1] frame]), 0, lineWidth, lineHeight);
lineView.center = CGPointMake(lineView.center.x, circleView.center.y);
lineView.backgroundColor = [self circleNormalColor];
}
circleViewX += lineWidth + circleView.frame.size.width;
}
if (self.currentProgress) {
self.currentProgress = self.currentProgress;
}
}

#pragma mark ---- private方法

- (UILabel *)labelWithTitle:(NSString *)title
{
UIFont *fontOfTitle = [self fontForTitle];
UIColor *colorOfTitle = [self titleNormalColor];
UILabel *label = [[UILabel alloc] init];
label.text = title;
label.textAlignment = NSTextAlignmentCenter;
label.textColor = colorOfTitle;
label.font = fontOfTitle;
return label;
}

- (void)circleViewWithView:(UIView *)view
{
UIColor *colorOfCircle = [self circleNormalColor];
view.layer.masksToBounds = YES;
view.layer.cornerRadius = view.frame.size.width / 2.0;
view.backgroundColor = colorOfCircle;
}

- (CGFloat)radiusForCircle
{
if ([self.delegate respondsToSelector:@selector(radiusForCircleViewInProgressView:)]) {
return [self.delegate radiusForCircleViewInProgressView:self];
}
return DefaultRadius;
}

- (UIFont *)fontForTitle
{
if ([self.delegate respondsToSelector:@selector(fontForTitleViewInProgressView:)]) {
return [self.delegate fontForTitleViewInProgressView:self];
}
return DefaultFont;
}

- (UIColor *)circleNormalColor
{
if ([self.delegate respondsToSelector:@selector(colorForCircleViewInProgressView:)]) {
return [self.delegate colorForCircleViewInProgressView:self];
}
return DefaultCircleColor;
}

- (UIColor *)circleHighColor
{
if ([self.delegate respondsToSelector:@selector(highlightColorForCircleViewInProgressView:)]) {
return [self.delegate highlightColorForCircleViewInProgressView:self];
}
return DefaultHighCircleColor;
}

- (UIColor *)titleNormalColor
{
if ([self.delegate respondsToSelector:@selector(colorForTitleViewInProgressView:)]) {
return [self.delegate colorForTitleViewInProgressView:self];
}
return DefaultTitleColor;
}

- (UIColor *)titleHighColor
{
if ([self.delegate respondsToSelector:@selector(highlightColorForTitleViewInProgressView:)]) {
return [self.delegate highlightColorForTitleViewInProgressView:self];
}
return DefaultHighTitleColor;
}

- (void)statusViewForCurrentProgress:(int)currentProgress
{
int numberOfProgress = (int)[self.dataSource numberOfProgressInProgressView];
UIColor *colorOfTitle = [self titleNormalColor];
UIColor *colorOfCircle = [self circleNormalColor];
for (int i = 0; i < numberOfProgress; i++) {
UILabel *label = self.titles[i];
label.textColor = colorOfTitle;

UIView *circleView = self.circles[i];
circleView.backgroundColor = colorOfCircle;

if (i != 0) {
UIView *lineView = self.lines[i - 1];
lineView.backgroundColor = colorOfCircle;
}
}

for (int i = 0; i < currentProgress; i++) {
UILabel *label = self.titles[i];
label.textColor = [self titleHighColor];

UIView *circleView = self.circles[i];
circleView.backgroundColor = [self circleHighColor];

if (i != 0) {
UIView *lineView = self.lines[i - 1];
lineView.backgroundColor = [self circleHighColor];
}
}
}

- (void)willMoveToSuperview:(UIView *)newSuperview
{
[self reloadData];
}
@end


可以看到,我就是按照UITableView的设计思路来实现这样一个进度条的(其实还是有很多其他更好的方法实现的)。有一个dataSource专为询问所要的数据,一个delegate专处理各种事件(事实上,内部高度、间距、颜色等改变,应该是放在delegate里面的,具体可以看UITableViewDelegate的设计)。

如果,不需要这个进度条通用,加入一个进度条只有四个进度,那么我会这么做:

用一个xib文件来描述所需4个的UILabel、四个圆圈可以用UIView实现,三根线条也是UIView实现,然后默认颜色为灰色,然后拉线出来,再根据具体情况改变其高亮状态的颜色即可......但是这样做,扩展性及其不好,即使只是要多加入一个状态,就得重新布局xib文件

如此,我想到了,为何不设计一个通用的进度条?这样以后遇到进度条的View,我直接把文件拖过来就是了......于是,它出来了。

在dataSource协议里面,我只是需要外界给我具体的进度数目、每个进度对应的标题。而delegate里面,说复杂呢,其实完全没必要实现那么多方法,毕竟我都是设置为@optional,只是考虑到代码的通用性以及同时用起来的舒畅性(主要是太懒,后期用到,不想再来修改)才将当时考虑到得都写上的。

这样,得到dataSource的数据之后,在.m文件里面,我就只需要考虑要创建多少个UIView和UILabel的问题了,然后就是排版~~简直不要太容易

当然,项目后期有时候中间一些状态是不高亮也要可以跳过,也是就加了个数组,让数组内对应下标的View和Label颜色改变下~~

(此篇只是按照我自己对iOS开发的理解所写,如果有错误的地方,还请指明,谢谢~~)

本progressBar的git地址:https://github.com/wzpziyi1/ZYProgressViewMode/tree/master/ZYProgressViewTest
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: