餅圖模塊, 詳細模塊控制項封裝 餅圖封裝分為三個控制項. 餅圖控制項 描述控制項 餅圖控制項(左)描述控制項(右)整合 圖為將兩個控制項包裝好了以後的樣子稱為BigBackgroundView控制項 BigBackgroundView控制項: BigBackground控制項中提供的方法 從方法可以看出只要傳入pieV ...
餅圖模塊, 詳細模塊控制項封裝
- 餅圖封裝分為三個控制項.
- 餅圖控制項
- 描述控制項
- 餅圖控制項(左)描述控制項(右)整合
圖為將兩個控制項包裝好了以後的樣子稱為BigBackgroundView控制項
BigBackgroundView控制項:
BigBackground控制項中提供的方法
/**
* 傳入控制項的內邊距 創建整體控制項
*
* @param pieViewInset 餅圖的內邊距(相對於最近控制項)
* @param describeViewInset 描述控制項的內邊距(相對於最近控制項)
*
* @return self 創建是不需要設置frame, 如設置 寬高傳入(0, 0)即可
*/
- (instancetype)bigViewWithPieViewEdgeInset:(UIEdgeInsets)pieViewInset DescribeViewEdgeInset:(UIEdgeInsets)describeViewInset;
- 從方法可以看出只要傳入pieView的內邊距和傳入描述控制項內邊距即可創建
- BigBackgroundView控制項不需要初始化frame,因為整體寬高是由內部的兩個控制項共同決定的
- 如果pieView的高度大於描述控制項的高度, 描述控制項居中.反之PieView居中
- 如果覺得BigBackgroundView控制項的左右佈局不夠你的開發需求,我們可以將兩個控制項拆分開來逐個排布
BigBackground中提供的屬性
PieView 和 DescribeView 的屬性彙總 下麵介紹這兩個控制項
PieView控制項
PieView中的屬性
/** 傳入百分比數組 */
@property(nonatomic, strong) NSArray *scaleArr;
/** 顏色數組 */
@property(nonatomic, strong) NSArray *colorArr;
/** 線寬 預設 20 當線寬等於餅圖半徑時候 空心變成實心 */
@property(nonatomic, assign) CGFloat lineWidth;
/** 餅圖半徑 預設35 當線寬等於餅圖半徑時候 空心變成實心 */
@property(nonatomic, assign) CGFloat pieViewRadius;
屬性 百分比數組
屬性 顏色數組(可以多傳顏色)
屬性 線寬(餅圖, 空心餅圖)
屬性 半徑(和線寬配合使用)
PieView中的方法
/**
* 一 傳入的百分比數組, 返迴轉換為餅圖使用的數組
*
* @param scaleArr 百分比數組
*
* @return pieView可用數組
*/
- (NSArray *)changeScaleArrayToPieViewArray:(NSArray *)scaleArr;
- 第一步: 需要將伺服器傳進來的百分比數組進行加工, 需要適應PieView中使用的百分比數組
- 解析: 餅圖的繪製, 第一個百分比繪製結束的地方, 就是第二個繪製的開始, 以此類推..
/**
* 二 創建餅圖方法
*
* @return self 不必設置frame
*/
- (instancetype)PieViewMaker;
- 第二步: 餅圖製造者, 直接調用此方法就可以繪製出餅圖.不必設置frame, 只需要給出x, y 即可, 寬高 (0, 0)即可.
- 如果不主動設置餅圖的半徑和線寬屬性, 則會初始化預設的餅圖.屬性中將預設數值已經列出
- 如果不傳入數組, 傳入數組數量不匹配, 控制器將不顯示控制項
DescribeView控制項
DescribeView中屬性
typedef NS_ENUM(NSInteger, DesPositionType) {
kPositionTypeHorizontal = 0, //橫向排列
kPositionTypeVertical //豎向排列
};
@interface DescribeView : UIView
/** 橫豎向排列 預設豎向排列*/
@property(nonatomic, assign) DesPositionType positionStyle;
/** 按鈕和label 字體大小 預設大小9 */
@property(nonatomic, assign) int labelFont ;
/** 控制項內部按鈕 和 label 空隙 預設5 */
@property(nonatomic, assign) CGFloat desButtonAndLabelMargin;
/** 控制項之間的行高 預設5 */
@property(nonatomic, assign) CGFloat rowMargin;
/** 折行 預設3 */
@property(nonatomic, assign) int breakLineNumber;
/** label最大寬度 預設有多寬顯示多寬*/
@property(nonatomic, assign) CGFloat maxLabelWidth;
/** view視圖之間的間隔 預設5*/
@property(nonatomic, assign) CGFloat colMargin;
屬性 橫向排布, 還是豎向排布
屬性 字體的大小
屬性 左邊方塊和文字之間的空隙
屬性 行間距
屬性 到第幾行折返到下一行(行, 列公用屬性)
屬性 文字最大顯示多寬(預設有多寬顯示多寬, 伺服器應該計算好寬度)
屬性 列間距
DescribeView中方法
/**
* 創建描述控制項
*
* @param describeArr 描述信息
* @param colorArr 顏色
* @param scaleArr 百分比
*
* @return self 創建不必設置frame
*/
- (instancetype)describeInPieView:(NSArray *)describeArr withColorArr:(NSArray *)colorArr withScaleArr:(NSArray *)scaleArr;
- 傳入描述信息數組, 顏色數組, 百分比數組, 將創建好一個描述控制項
- 如果不設置描述控制項屬性則顯示預設屬性, 以上屬性有列出
- 如果傳入數組為空, 輸入數組數量不一致, 控制器將不顯示描述控制項
控制項解析
PieView解析
應用drawRect:
方法繪製
- 進入PieView首先先應用
drawRect:
方法先繪製第一個圓- 因為如果伺服器由於精度問題, 傳入的百分比數組相加不為1, 有可能少, 有可能多. 我先繪製一個完整圓, 剩下的繼續繪製, 到最後會空出一個位置, 正好最下麵的整個員從空出的位置顯示出來, 將位置補充完成, 解決伺服器返回數據不准確問題
#pragma mark - 直接繪製 drawRect
- (void)drawRect:(CGRect)rect {
if (self.colorArr.count < self.scaleArr.count) {
return;
}
// 如果傳入數組數量為零 則直接返回
if (self.scaleArr.count == 0) {
return;
} else {
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:self.center radius:_pieRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
[self adjust:path];
[path stroke];
}
}
- 裡面有兩個容錯
- 一個是數組為零時候
- 一個是顏色少於百分比數組的時候
百分比數組轉換成餅圖使用數組
- 由於伺服器返回數組為
@[@0.1, @0.2, @0.3, @0.4];
- 而餅圖需要的是第一個元素為起點, 第二個元素為終點; 第二個元素為起點, 第三個元素為終點...依次類推直到結束, 這樣的數組
#pragma mark - 將百分比穿換成pieView使用的百分比數組
- (NSArray *)changeScaleArrayToPieViewArray:(NSArray *)scaleArr
{
CGFloat total = 0.0f;
CGFloat start = 0.0f;
CGFloat end = 0.0f;
NSMutableArray *mScaleArr = [NSMutableArray array];
for (int i = 0; i < scaleArr.count; i ++) {
total += [scaleArr[i] floatValue];
end = M_PI * 2 * [scaleArr[i] floatValue] + start;
start = end;
[mScaleArr addObject:[NSNumber numberWithFloat:end]];
}
return [mScaleArr copy];
}
- 轉換完成後返回一個餅圖可用的數組
最後調用餅圖製造者方法
- 裡面使用到了
UIBezierPath
,CAShapeLayer
這兩個類 - 先繪製
path
, 然後添加到shapLayer
上面
#pragma mark - 餅圖製造者
- (instancetype)PieViewMaker
{
if (self.colorArr.count < self.scaleArr.count) {
return nil;
}
//當線寬大於等於半徑時候, 將為實心圓
if (self.lineWidth >= self.pieViewRadius) {
self.lineWidth = self.pieViewRadius;
}
CGRect pieViewFrame = self.frame;
pieViewFrame.size.width = self.pieViewRadius * 2;
pieViewFrame.size.height = self.pieViewRadius * 2;
self.frame = pieViewFrame;
//裡面空心圓半徑
_pieRadius = (self.frame.size.width - self.lineWidth) / 2;
// 如果傳入數組數量為零 則直接返回
if (self.scaleArr.count == 0) {
return nil;
}
//如果傳入數組數量為1, 則繪製整個圓
if (self.scaleArr.count == 1) {
return nil;
} else {
//否則正常繪製
for (int i = 0; i <= self.scaleArr.count - 2; i++) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:self.center radius:_pieRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
[self adjust:path];
[path stroke];
_bezierPath = [UIBezierPath bezierPath];
[_bezierPath addArcWithCenter:self.center radius:_pieRadius startAngle:[self.scaleArr[i] floatValue] endAngle:[self.scaleArr[i + 1] floatValue] clockwise:YES];
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.frame = self.bounds;
_shapeLayer.path = _bezierPath.CGPath;
[self adjustPathAndShapeLayer:_bezierPath shapeLayer:_shapeLayer color:((UIColor *)self.colorArr[i + 1])];
[self.layer addSublayer:_shapeLayer];
}
}
return self;
}
- 返回self, 在裡面反推寬高
- 裡面做了容錯處理
DescribeView解析
- 根據描述數組中最大的字元串先計算出寬度, 使用KVC中的
@"@max.intValue"
方法
#pragma mark - 返回數組中最大值
- (float)returnMaxWidth:(NSArray *)numberArr
{
NSMutableArray *mTextArr = [NSMutableArray array];
for (int i = 0; i < numberArr.count; i++) {
CGRect frame = [numberArr[i] boundingRectWithSize:CGSizeMake(1000, 1000) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:_labelFont]} context:nil];
double width = frame.size.width;
[mTextArr addObject:[NSNumber numberWithFloat:width]];
}
return [[mTextArr valueForKeyPath:@"@max.intValue"] floatValue] + 1;
}
- 控制項整體使用frame計算寬高
- 返回self, 在空間內部計算寬高
- 根據行列, 根據行折行數量, 計算孔家顯示排布
#pragma mark - 創建描述控制項
- (instancetype)describeInPieView:(NSArray<NSString *> *)describeArr withColorArr:(NSArray *)colorArr withScaleArr:(NSArray<NSNumber *> *)scaleArr
{
//容錯: 數組元素顯示不匹配將 直接return
if (colorArr.count < scaleArr.count || colorArr.count < describeArr.count || describeArr.count < scaleArr.count || scaleArr.count < describeArr.count) {
return nil;
}
//返回數組中最大字元串長度
CGFloat maxLabelWidth;
if (self.maxLabelWidth < 0.001) {
maxLabelWidth = [self returnMaxWidth:describeArr];
} else {
maxLabelWidth = self.maxLabelWidth;
}
for (int i = 0; i < describeArr.count; i++) {
[self setUpButtonWithDesArr:scaleArr colorArr:colorArr index:i];
}
if (describeArr.count == scaleArr.count) {
switch (self.positionStyle) {
case kPositionTypeVertical: // 豎向排列
{
for (int i = 0; i < describeArr.count; i++) {
//避免重賦值_viewHeight 和 _viewWidth
if (_viewHeight == 0.0) {
//顯示View的高度
_viewHeight = _buttonHeight;
//顯示View寬度
_viewWidth = _maxButtonWidth + maxLabelWidth + _desButtonAndLabelMargin;
//豎向排布frame計算
if (scaleArr.count <= _breakLineNumber) {
CGRect showViewWidth = self.frame;
showViewWidth.size.width = _viewWidth;
showViewWidth.size.height = (scaleArr.count - 1) * _rowMargin + scaleArr.count * _viewHeight;
self.frame = showViewWidth;
} else {
if (scaleArr.count % _breakLineNumber == 0) {
CGRect showViewWidth = self.frame;
showViewWidth.size.height = (scaleArr.count / _breakLineNumber) * _viewHeight + ((scaleArr.count / _breakLineNumber) - 1) * _rowMargin;
showViewWidth.size.width = (_colMargin * _breakLineNumber - 1) + _breakLineNumber * _viewWidth;
self.frame = showViewWidth;
} else {
CGRect showViewWidth = self.frame;
showViewWidth.size.width = ((scaleArr.count / _breakLineNumber) + 1) * _viewWidth + ((scaleArr.count / _breakLineNumber)) * _colMargin;
showViewWidth.size.height = (_colMargin * (_breakLineNumber - 1)) + _breakLineNumber * _viewHeight;
self.frame = showViewWidth;
}
}
}
//View 的x值
NSUInteger col = i / _breakLineNumber;
CGFloat viewX = col * (_viewWidth + _colMargin);
//View 的y值
NSUInteger row = i % _breakLineNumber;
CGFloat viewY = row * (_viewHeight + _rowMargin);
//創建按鈕顯示
UIButton * vScaleBtn = [self setUpButtonWithDesArr:scaleArr colorArr:colorArr index:i];
//創建文本詳細顯示
UILabel *vDesLabel = [self setUpLabelWithDescribeArr:describeArr index:i maxWidth:maxLabelWidth];
//創建底層View
UIView *showView = [[UIView alloc] initWithFrame:CGRectMake(viewX, viewY, _viewWidth, _viewHeight)];
// showView.backgroundColor = [UIColor redColor];
[showView addSubview:vDesLabel];
[showView addSubview:vScaleBtn];
[self addSubview:showView];
}
}
break;
//橫向排布
case kPositionTypeHorizontal:
{
for (int i = 0; i < describeArr.count; i++) {
//避免重賦值_viewHeight
if (_viewHeight == 0.0) {
//顯示View的高度
_viewHeight = _buttonHeight;
//顯示View寬度
_viewWidth = _maxButtonWidth + maxLabelWidth + _desButtonAndLabelMargin;
//橫向排布佈局
if (scaleArr.count <= _breakLineNumber) {
CGRect showViewWidth = self.frame;
showViewWidth.size.width = _colMargin * (scaleArr.count - 1) + scaleArr.count * _viewWidth;
showViewWidth.size.height = _viewHeight;
self.frame = showViewWidth;
} else {
if (scaleArr.count % _breakLineNumber == 0) {
CGRect showViewWidth = self.frame;
showViewWidth.size.height = (scaleArr.count / _breakLineNumber) * _viewHeight + ((scaleArr.count / _breakLineNumber) - 1) * _rowMargin;
showViewWidth.size.width = (_colMargin * _breakLineNumber - 1) + _breakLineNumber * _viewWidth;
self.frame = showViewWidth;
} else {
CGRect showViewWidth = self.frame;
showViewWidth.size.height = ((scaleArr.count / _breakLineNumber) + 1) * _viewHeight + ((scaleArr.count / _breakLineNumber)) * _rowMargin;
showViewWidth.size.width = (_colMargin * (_breakLineNumber - 1)) + _breakLineNumber * _viewWidth;
self.frame = showViewWidth;
}
}
}
//View 的x值
NSUInteger col = i % _breakLineNumber;
CGFloat viewX = col * (_viewWidth + _colMargin);
//View 的y值
NSUInteger row = i / _breakLineNumber;
CGFloat viewY = row * (_viewHeight + _rowMargin);
//創建按鈕顯示
UIButton *hScaleBtn = [self setUpButtonWithDesArr:scaleArr colorArr:colorArr index:i];
//創建文本按鈕
UILabel *hDesLabel = [self setUpLabelWithDescribeArr:describeArr index:i maxWidth:maxLabelWidth];
//創建底層View
UIView *showView = [[UIView alloc] initWithFrame:CGRectMake(viewX, viewY, _viewWidth, _viewHeight)];
[showView addSubview:hDesLabel];
[showView addSubview:hScaleBtn];
[self addSubview:showView];
}
}
break;
}
}
return self;
}
- 其中做了容錯處理
BigBackgroundView控制項解析
import以上兩個控制項
在這個控制項中包含了以上兩個控制項的所有屬性, 以便於直接調用修改- 創建控制項的時候直接傳入兩個控制項的內邊距, BigBackgroundView控制項內部會做處理
- 返回self創建控制項的時候不必設置frame, 只需要設置位置即可
#pragma mark - 創建控制項
- (instancetype)bigViewWithPieViewEdgeInset:(UIEdgeInsets)pieViewInset DescribeViewEdgeInset:(UIEdgeInsets)describeViewInset
{
//容錯: 數組元素顯示不匹配將 直接return
if (self.colorArr.count < self.scaleArr.count || self.colorArr.count < self.describeArr.count || self.describeArr.count < self.scaleArr.count || self.scaleArr.count < self.describeArr.count) {
return nil;
}
// self.backgroundColor = [UIColor grayColor];
[self setUpPieViewAndDesView];
_pieViewInset = pieViewInset;
_desViewInset = describeViewInset;
//間隔距離
_pieViewAndDesViewMargin = fabs(_pieViewInset.right) ;
//設置desView在bigView中的位置
CGRect desViewFrame = _desView.frame;
desViewFrame.origin.x = fabs(_pieViewInset.left) + self.basePieView.frame.size.width + _pieViewAndDesViewMargin;
desViewFrame.origin.y = fabs(_desViewInset.top) ;
_desView.frame = desViewFrame;
//設置pieView在bigView中的位置
CGRect basePieViewFrame = self.basePieView.frame;
basePieViewFrame.origin.x = fabs(_pieViewInset.left) ;
basePieViewFrame.origin.y = fabs(_pieViewInset.top) ;
self.basePieView.frame = basePieViewFrame;
//根據pieView 和 desView 計算bigView的寬 和 高
CGRect bigViewFrame = self.frame;
if (self.basePieView.frame.size.height > self.desView.frame.size.height) {
bigViewFrame.size.height = fabs(_pieViewInset.top) + self.basePieView.frame.size.height + fabs(_pieViewInset.bottom);
bigViewFrame.size.width = fabs(_pieViewInset.left) + self.basePieView.frame.size.width + _pieViewAndDesViewMargin + _desView.frame.size.width + fabs(_desViewInset.right);
self.frame = bigViewFrame;
//當pieView的高度 大於 desView的高度 desView 居中與PieView
CGPoint desViewCenter = _desView.center;
desViewCenter.y = self.basePieView.center.y;
self.desView.center = desViewCenter;
} else {
bigViewFrame.size.height = fabs(_desViewInset.top) + _desView.frame.size.height + fabs(_desViewInset.bottom);
bigViewFrame.size.width = fabs(_pieViewInset.left) + self.basePieView.frame.size.width + _pieViewAndDesViewMargin + _desView.frame.size.width + fabs(_desViewInset.right);
self.frame = bigViewFrame;
//當pieView的高度 小於 desView的高度PieView 居中與 desView
CGPoint basePieViewCenter = self.basePieView.center;
basePieViewCenter.y = self.desView.center.y;
self.basePieView.center = basePieViewCenter;
}
return self;
}
- 裡面做了容錯處理
- 傳入的內邊距做了絕對值處理
- 對傳入數組做了容錯處理
- 對邊距做了容錯處理
使用方面
- 我模擬了一個模型
DescriptionModel
//模型
DescriptionModel *model = [DescriptionModel new];
單獨使用PieView
警告⚠️:
1) 使用PieViews控制項, 一定要在調用 "changeScaleArrayToPieViewArray:" 方法前設置屬性.
2) 使用PieViews控制項, 一定要先調用轉換百分比方法 "changeScaleArrayToPieViewArray:" 再調用PieView製造者方法 "PieViewMaker"
3) 使用PieViews控制項, 一定要在 "addSubView" 之前 調用 "changeScaleArrayToPieViewArray:" 方法
4) 單獨使用PieViews控制項,需要自己在PieViews控制項外麵包一層View,否則移動時會出現錯亂.
PieView使用順序:
設置屬性 > 調用"changeScaleArrayToPieViewArray:" > 調用"PieViewMaker" > 包一層View > addSubViw
/***********單獨使用**************/
PieViews *pieView = [PieViews new];
pieView.lineWidth = 30;
pieView.pieViewRadius = 60;
pieView.colorArr = model.colorArr;
pieView.scaleArr = [pieView changeScaleArrayToPieViewArray:model.scaleArray];
[pieView PieViewMaker];
//包一層View
UIView *basePieView = [UIView new];
basePieView.frame = CGRectMake(20, 40, pieView.frame.size.width, pieView.frame.size.height);
[basePieView addSubview:pieView];
[self.view addSubview:basePieView];
/********************************/
- 控制項創建的時候不需要設置frame
- 需要包一層View, 繪製圖形的通病
- 如果要設置位置, 只需要改變外麵包的那層view的位置即可
單獨使用DescribeView
無特殊警告⚠️
/***********單獨使用**************/
DescribeView *desView = [DescribeView new];
desView.frame = CGRectMake(20, pieView.frame.size.height + 50, 0, 0);
desView.labelFont = 10;
desView.desButtonAndLabelMargin = 5;
desView.rowMargin = 5;
desView.colMargin = 5;
desView.breakLineNumber = 3;
desView.positionStyle = kPositionTypeVertical;
[desView describeInPieView:model.descriptionArray withColorArr:model.colorArr withScaleArr:model.scaleArray];
[self.view addSubview:desView];
/********************************/
整體使用BigBackgroundView
警告⚠️
1) 使用BigBackGroundView控制項時, 一定要在調用 "bigViewWithPieViewEdgeInset: DescribeViewEdgeInset:"方法前設置屬性.
2) 設置center屬性時, 要在 "bigViewWithPieViewEdgeInset: DescribeViewEdgeInset:"方法後設置. 直接設置frame無所謂.BigBackgroundView使用順序:
設置屬性 > "bigViewWithPieViewEdgeInset: DescribeViewEdgeInset:" > 設置 center 屬性(不瞭解為什麼)
/***************整合*****************/
BigBackgroundView *showView = [BigBackgroundView new];
showView.pieViewRadius = 60;
showView.lineWidth = 30;
showView.positionStyle = kPositionTypeVertical;
showView.breakLineNumber = 5;
showView.colorArr = model.colorArr;
showView.scaleArr = model.scaleArray;
showView.describeArr = model.descriptionArray;
[self.view addSubview:showView];
[showView bigViewWithPieViewEdgeInset:UIEdgeInsetsMake(0, 0, 0, 10) DescribeViewEdgeInset:UIEdgeInsetsMake(0, 0, 0, 0)];
showView.center = self.view.center;
/********************************/