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

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

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

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

在这里,得说说我对iOS开发中数据源协议(dataSource)和代理协议(delegate)的理解,个人认为主要是为遵守这些个协议的类新增一些方法,而这些方法用来与拥有dataSourcedelegate的那个类进行通信(也可以说是处理事件、传递数据等)。比如说,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

原文地址:https://www.cnblogs.com/ziyi--caolu/p/4769703.html