iOS开发-一个简单的自定义柱状图(可扩展)

首先,我们先来看一下实现的效果图:


柱状图

从整体上来看,柱状图其实就是一个自定义的UIView,在view中添加了相关的子View对象。针对代码层面来说的话,代码中的注释都很详细,简显易懂,这里就不做解释了。请直接看代码:
LGJHistogramView.h

//
//  LGJHistogramView.h
//  LGJDemo
//
//  Created by ADIQueen on 2019/8/9.
//  Copyright © 2019年 com.harego. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

typedef void(^HistogramViewBlock)(NSInteger inde);

@interface LGJHistogramView : UIView

@property(nonatomic,assign)float pillarsWidth;//柱子的宽度 默认是18pt

@property(nonatomic,assign)float pillarsNumber;//柱子数量  默认0

@property(nonatomic,assign)float pillarsMargin;//柱子间距 默认26

@property(nonatomic,assign)NSInteger vertVersion; //纵向等分数量 默认5

@property(nonatomic,strong)NSString *saidMeaning;//柱状图的表示含义 例如:分数---成绩---得分  默认是分数

@property(nonatomic,assign)BOOL showleftLine;//是否显示左侧边

@property(nonatomic,strong)UIColor *bottomTextColor;//底部文字颜色

@property(nonatomic,strong)UIColor *leftTextColor;//左侧文字颜色

@property(nonatomic,strong)UIColor *pillarsTopTextColor;//柱子上面具体的小数字的颜色

@property(nonatomic,assign)BOOL showPillarsTopText;//是否显示柱子上面具体的小数字 默认YES

/**
 柱子的点击事件
 */
@property(nonatomic,copy)HistogramViewBlock clickBlock;

/**
 数据容器
 */
@property(nonatomic,strong)NSArray *keyValues;


@end

NS_ASSUME_NONNULL_END

LGJHistogramView.m

//
//  LGJHistogramView.m
//  LGJDemo
//
//  Created by ADIQueen on 2019/8/9.
//  Copyright © 2019年 com.harego. All rights reserved.
//

#import "LGJHistogramView.h"
#import "LGJTableData.h"

#define Left_Margin 34
#define Right_Margin 16

//RGB颜色
#define colorWithRGB(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0]
#define colorWithRGBAlpha(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(r)/255.0 blue:(r)/255.0 alpha:a]
#define colorWithRGBValue(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]
#define colorWithSameRGB(rgb) [UIColor colorWithRed:(rgb)/255.0 green:(rgb)/255.0 blue:(rgb)/255.0 alpha:1.0]


@interface LGJHistogramView ()

@property(nonatomic,assign)float rowSpace;//行距

@property(nonatomic,assign)float bigestNumber;//最大值

@property(nonatomic,assign)float con_pillars_margin;//底部文字距离柱子间距 默认是8

/**
 下面的三个属性都不需要设置 都是根数传入的数据源获取的
 */
@property(nonatomic,strong)NSMutableArray *ys; //y轴坐标 [0,2,4,6,8,10]

@property(nonatomic,strong)NSArray *xs; //x轴坐标 默认为空

@property(nonatomic,strong)NSArray *numbers; //y轴实际值 默认为空

@end

@implementation LGJHistogramView

//初始化
-(instancetype)initWithFrame:(CGRect)frame{
    
    if (self = [super initWithFrame:frame]) {
        
        _vertVersion = 5;
        
        _pillarsWidth = 18;
        
        _pillarsMargin = 26;
        
        _con_pillars_margin = 8;
        
        _showPillarsTopText = YES;
        
    }
    return self;
}

//柱状图的绘制
- (void)drawRect:(CGRect)rect {
    
    //创建画布
    CGContextRef ctr = UIGraphicsGetCurrentContext();
    
    [colorWithRGB(235, 235, 235) set];
    
    CGContextSetLineWidth(ctr, 1);
    
    //横向分隔线
    for (NSInteger index = 0; index < 6; index++) {
        CGContextMoveToPoint(ctr, Left_Margin, _rowSpace * (index + 2));
        CGContextAddLineToPoint(ctr, self.frame.size.width - Left_Margin - Right_Margin, _rowSpace * (index + 2));
        CGContextStrokePath(ctr);
    }
    
    //左侧边
    if (self.showleftLine) {
        
        CGContextMoveToPoint(ctr, Left_Margin, _rowSpace * 4 / 3.0);
        CGContextAddLineToPoint(ctr, Left_Margin, _rowSpace * ((_vertVersion + 2)));
        CGContextStrokePath(ctr);
        
    }
    
    
    //绘制分数分割数字
    NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
    NSDictionary *attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:12],NSParagraphStyleAttributeName:paragraph,NSForegroundColorAttributeName:colorWithRGBValue(0x9b9b9b)};
    
    for (NSInteger inde = 0; inde < self.ys.count; inde++) {
        NSString *scaleValue = self.ys[(self.ys.count - 1) - inde];
        paragraph.alignment = NSTextAlignmentCenter;
        
        if (_leftTextColor) {
            NSDictionary *attribute1 = @{NSFontAttributeName:[UIFont systemFontOfSize:12],NSParagraphStyleAttributeName:paragraph,NSForegroundColorAttributeName:_leftTextColor};
            [scaleValue drawInRect:CGRectMake(0, _rowSpace * (inde + 1) + _rowSpace / 2, Left_Margin, _rowSpace) withAttributes:attribute1];
        }else{
            [scaleValue drawInRect:CGRectMake(0, _rowSpace * (inde + 1) + _rowSpace / 2, Left_Margin, _rowSpace) withAttributes:attribute];
        }
    }
    
    //绘制分数两个字
    if (_saidMeaning) {
        [_saidMeaning drawInRect:CGRectMake(Left_Margin / 4, _rowSpace / 1.5 , Left_Margin, _rowSpace) withAttributes:attribute];
    }else{
        [@"分数" drawInRect:CGRectMake(Left_Margin / 4, _rowSpace / 1.5 , Left_Margin, _rowSpace) withAttributes:attribute];
    }
    
    //绘制柱子
    for (NSInteger index = 0; index < self.xs.count; index++) {
        
        CGFloat number = [self.numbers[index] floatValue];
        
        CGFloat scale =  number / _bigestNumber;
        
        UIButton *barBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        
        CGFloat x = _pillarsMargin * (index + 1) + _pillarsWidth * (index) + Left_Margin / 2.0;
        CGFloat height = _rowSpace * _vertVersion * scale;
        CGFloat y = _rowSpace * (_vertVersion + 2) - height;
        CGFloat width = _pillarsWidth;
        
        
        barBtn.frame = CGRectMake(x,  _rowSpace * _vertVersion + _rowSpace * 2, _pillarsWidth, 0);
        barBtn.tag = index;
        [self addSubview:barBtn];
        [barBtn addTarget:self action:@selector(barChartClick:) forControlEvents:UIControlEventTouchUpInside];
        [UIView animateWithDuration:1.0 animations:^{
            barBtn.frame = CGRectMake(x, y, width, height);
            barBtn.backgroundColor = [self colorWithIndex:(int)index];
        }];
        
        //横向文字
        NSString *itemStr = self.xs[index];
        
        if (_bottomTextColor) {
            NSDictionary *attribute1 = [attribute copy];
            attribute1 = @{NSFontAttributeName:[UIFont systemFontOfSize:12],NSParagraphStyleAttributeName:paragraph,NSForegroundColorAttributeName:_bottomTextColor};
            [itemStr drawInRect:CGRectMake(_pillarsMargin * index + _pillarsMargin / 2.0 + _pillarsWidth * index + + Left_Margin / 2.0, _rowSpace * (_vertVersion + 2) + _con_pillars_margin ,_pillarsMargin + _pillarsWidth ,_rowSpace) withAttributes:attribute1];
        }else{
            [itemStr drawInRect:CGRectMake(_pillarsMargin * index + _pillarsMargin / 2.0 + _pillarsWidth * index + + Left_Margin / 2.0, _rowSpace * (_vertVersion + 2) + _con_pillars_margin ,_pillarsMargin + _pillarsWidth ,_rowSpace) withAttributes:attribute];
        }
        
        // 分数
        if (_showPillarsTopText) {
            if (_pillarsTopTextColor) {
               attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:12],NSParagraphStyleAttributeName:paragraph,NSForegroundColorAttributeName:_pillarsTopTextColor};
            }
            NSString *actureScore = self.numbers[index];
            //绘制每个item的得分
            [actureScore drawInRect:CGRectMake(_pillarsMargin * index + _pillarsMargin / 2.0 + _pillarsWidth * index + Left_Margin / 2.0, y - _rowSpace / 2.0 ,_pillarsMargin + _pillarsWidth ,_rowSpace) withAttributes:attribute];
        }
        
    }
}

/**
 获取临接柱子的颜色
 */
-(UIColor *)colorWithIndex:(int)index{
    UIColor *color;
    NSInteger num = index % 7;
    switch (num) {
        case 0:
            color = colorWithRGB(122, 174, 235);
            break;
        case 1:
            color = colorWithRGB(130, 224, 181);
            break;
        case 2:
            color = colorWithRGB(235, 195, 122);
            break;
        case 3:
            color = colorWithRGB(122, 174, 235);
            break;
        case 4:
            color = colorWithRGB(236, 145, 143);
            break;
        case 5:
            color = colorWithRGB(235, 195, 122);
            break;
        default:
            color = colorWithRGB(130, 224, 181);
            break;
    }
    return color;
}


/**
 有的时候一些要求柱子能够点击 这个方法是点击柱子的回调
 方法
 */
-(void)barChartClick:(UIButton *)button{
    
    if (self.clickBlock) {
        
        self.clickBlock(button.tag);
        
    }
    
}


#pragma mark------setter getter

-(void)setShowleftLine:(BOOL)showleftLine{
    
    _showleftLine = showleftLine;
    
}

-(void)setSaidMeaning:(NSString *)saidMeaning{
    
    _saidMeaning = saidMeaning;
    
}

-(void)setBottomTextColor:(UIColor *)bottomTextColor{
    
    _bottomTextColor = bottomTextColor;
}

-(void)setLeftTextColor:(UIColor *)leftTextColor{
    
    _leftTextColor = leftTextColor;
}

-(void)setShowPillarsTopText:(BOOL)showPillarsTopText{
    
    _showPillarsTopText = showPillarsTopText;
}


-(void)setPillarsTopTextColor:(UIColor *)pillarsTopTextColor{
    
    _pillarsTopTextColor = pillarsTopTextColor;
}


-(void)setKeyValues:(NSArray *)keyValues{
    
    _keyValues = keyValues;
    
    _pillarsNumber = [self.xs count];
    
    _rowSpace = self.frame.size.height /(_vertVersion + 3.0);
    
    _bigestNumber = [self getMaxFloatNumberFrom:self.numbers];
    
    if (_bigestNumber == 0) {
        
        _bigestNumber = 10;
        
    }
    
    CGFloat maxWidth = [self getMaxContentWidth:self.xs];
    
    if (maxWidth <= _pillarsWidth) {
        
        _pillarsMargin = _pillarsWidth + 10;
        
    }else{
        
        _pillarsMargin = maxWidth;
        
    }
}

/**
 给定一组数据 求出数据中的最大值 最大值返回值是一个整数
 因为在此纵轴的分割以整数为单位
 */
-(CGFloat)getMaxFloatNumberFrom:(NSArray *)numbers{
    //和上面这个方法一样 只不过返回了 浮点数
    //因为刻画纵向刻度整数 而绘制柱子则要的就是精确一点的数字
    
    CGFloat max = [numbers[0] floatValue];
    
    for (NSInteger inde = 1; inde < numbers.count; inde++) {
        
        CGFloat number = [numbers[inde] floatValue];
        
        if (number > max) {
            
            max = number;
        }
    }
    return max;
}


/**
 给定一组字符串 根据字符串中的内容获取字符串的最大宽度
 整体上各个柱子之间的间距以字符串的最大宽度为基准
 */
-(CGFloat)getMaxContentWidth:(NSArray *)contents{
    
    CGFloat maxWidth = 0.0;
    
    for (NSInteger inde= 0; inde < contents.count ; inde++) {
        
        NSString *str = contents[inde];
        
        CGSize sizeToFit = [str boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, _rowSpace) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12.0]} context:nil].size;
        
        if (sizeToFit.width > maxWidth) {
            
            maxWidth = sizeToFit.width;
        }
    }
    return maxWidth;
}



#pragma mark------Lazy Loading

-(NSArray *)xs{
    
    if (!_xs) {
        
        NSMutableArray *array = [NSMutableArray array];
        
        for (NSInteger inde = 0; inde < self.keyValues.count; inde++) {
            
            LGJTableData *data = self.keyValues[inde];
            
            [array addObject:data.itemStr];
            
        }
        _xs = [array copy];
    }
    return _xs;
    
}

-(NSMutableArray *)ys{
    
    if (!_ys) {
        
        _ys = [NSMutableArray arrayWithObjects:@"0", nil];
        
        NSMutableArray *array = [NSMutableArray array];
        
        for (NSInteger inde = 0; inde < self.keyValues.count; inde++) {
            
            LGJTableData *data = self.keyValues[inde];
            
            [array addObject:data.itemValue];
            
        }
        
        NSInteger max = [self getMaxFloatNumberFrom:[array copy]];
        
        //每个纵格的数字之差
        CGFloat difference = max / _vertVersion;
        
        for (NSInteger ind = 0; ind < _vertVersion; ind++) {
            
            [_ys addObject:[NSString stringWithFormat:@"%ld",(NSInteger)((ind + 1) * difference)]];
        }
    }
    return _ys;
}


-(NSArray *)numbers{
    
    if (!_numbers) {
        
        NSMutableArray *array = [NSMutableArray array];
        
        for (NSInteger inde = 0; inde < self.keyValues.count; inde++) {
            
            LGJTableData *data = self.keyValues[inde];
            
            [array addObject:data.itemValue];
            
        }
        _numbers = [array copy];
    }
    return _numbers;
    
}
@end

//LGJTableData.h

//
//  LGJTableData.h
//  LGJDemo
//
//  Created by ADIQueen on 2019/8/11.
//  Copyright © 2019年 com.harego. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LGJTableData : NSObject

@property(nonatomic,copy)NSString *itemStr; //横向选项

@property(nonatomic,copy)NSString *itemValue;//纵向实际值

@end

NS_ASSUME_NONNULL_END

//以上的话是具体的实现代码,那么如果我们要在某个地方用的话 也是非常的方便。下面举一个简单的应用例子:

-(void)addSubView{
    
    
    _histogramView = [[LGJHistogramView alloc]initWithFrame:CGRectMake(0, 100, self.view.frame.size.width, 300)];
    _histogramView.backgroundColor = [UIColor whiteColor];
    
    /*
     做对应的属性设置 这里省略了。。。。
     */
    
    LGJTableData *data = [LGJTableData new];
    data.itemStr = @"姑姑";
    data.itemValue = @"7";
    LGJTableData *data1 = [LGJTableData new];
    data1.itemStr = @"嘎嘎嘎";
    data1.itemValue = @"10";
    LGJTableData *data2 = [LGJTableData new];
    data2.itemStr = @"哈哈哈哈哈";
    data2.itemValue = @"9";
    NSArray *array = @[data,data1,data2];
    
    _histogramView.keyValues = array; //传入一个模型数组,作为页面绘制的数据源
    
    [self.view addSubview:_histogramView];
    
}

跑起来程序 就是开篇的图片效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容