• 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
Copyright © 2020-2023  润新知