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

2019-08-11  本文已影响0人  见哥哥长高了

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


柱状图

从整体上来看,柱状图其实就是一个自定义的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];
    
}

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

上一篇下一篇

猜你喜欢

热点阅读