在新的開發(fā)版本中,遇到一個這樣的需要,實現(xiàn)一個類似刻度尺的樣式,UI如下:第一眼看到這個需求的時候,我的第一感覺,就是使用圖形畫線,在進行一番思索之后,我就開始demo階段,首先分析這個UI有哪些部分組成:
- 刻度尺(高低不同)
- 選中區(qū)域
- 刻度尺顏色
- 選中區(qū)域顏色
- 選中區(qū)域的刻度尺的顏色
- 刻度尺第一和最后一個刻度尺單獨處理不顯示
- 刻度尺的寬度
……

WechatIMG1.jpeg
- 第一種實現(xiàn)思路,首先我想到的是自定義UIView,其實設(shè)計方法很簡單,頭文件代碼如下:
/** 選中的數(shù)值 */
@property (nonatomic, assign) CGFloat selectedValue;
/** 最小值 */
@property (nonatomic, assign) NSInteger minValue;
/** 最大值 */
@property (nonatomic, assign) NSInteger maxValue;
/** 小刻度分成幾塊 */
@property (nonatomic, assign) NSInteger splitNumber;
/** 主刻度長度,默認(rèn)值 10.0 */
@property (nonatomic, assign) CGFloat majorScaleLength;
/** 小刻度長度,默認(rèn)值 5.0 */
@property (nonatomic, assign) CGFloat minorScaleLength;
/** 刻度尺寬度 */
@property (nonatomic, assign) CGFloat scaleWidth;
/** 指示條顏色 */
@property (nonatomic, strong) UIColor *indicatorColor;
/** 背景顏色 */
@property (nonatomic, strong) UIColor *rulerBackgroundColor;
/** 指示條的顏色 */
@property (nonatomic, strong) UIColor *rulerColor;
/** 選中的刻度尺的顏色 */
@property (nonatomic, strong) UIColor *selectedRuleColor;
這些就是暴露給使用者使用的屬性,當(dāng)然有些業(yè)務(wù)需要在里面,有些屬性我就直接在內(nèi)部寫死了,當(dāng)然也是可以抽取出來的。.m文件的實現(xiàn)也很簡單:
#import "YCRulerView.h"
/** 主刻度長度默認(rèn)值 */
static CGFloat const kMajorScaleDefaultLength = 13.0;
/** 小刻度長度默認(rèn)值 */
static CGFloat const kMinorScaleDefaultLength = 5.0;
@interface YCRulerView ()
/** 指示器的view */
@property (nonatomic, strong) UIView *indicatorView;
@end
@implementation YCRulerView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setupUI];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self setupUI];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self reloadRuler];
}
- (void)setupUI {
self.indicatorView = [[UIView alloc] init];
[self addSubview:self.indicatorView];
}
- (void)reloadRuler {
self.backgroundColor = self.rulerBackgroundColor;
// 每一條刻度線之前的間距
CGFloat space = (self.frame.size.width - self.maxValue * self.scaleWidth) / self.maxValue;
for (int i = 1; i <= _maxValue; i ++) {
CGFloat viewX = space + (i - 1) * (self.scaleWidth + space);
UIView *lineView = [[UIView alloc] init];
if (i <= self.selectedValue) {
lineView.backgroundColor = self.selectedRuleColor;
} else {
lineView.backgroundColor = self.rulerColor;
}
if (i == 0) {
lineView.frame = CGRectZero;
} else if (i == _maxValue) {
lineView.frame = CGRectZero;
} else {
if (i % self.splitNumber == 0) {
lineView.frame = CGRectMake(viewX, self.frame.size.height - self.majorScaleLength, self.scaleWidth, self.majorScaleLength);
} else {
lineView.frame = CGRectMake(viewX, self.frame.size.height - self.minorScaleLength, self.scaleWidth, self.minorScaleLength);
}
}
[self addSubview:lineView];
}
self.indicatorView.backgroundColor = self.indicatorColor;
self.indicatorView.frame = CGRectMake(0, 0, self.frame.size.width * self.selectedValue / self.maxValue, self.frame.size.height);
}
#pragma mark - 屬性默認(rèn)值
- (CGFloat)majorScaleLength {
if (_majorScaleLength <= 0) {
_majorScaleLength = kMajorScaleDefaultLength;
}
return _majorScaleLength;
}
- (CGFloat)minorScaleLength {
if (_minorScaleLength <= 0) {
_minorScaleLength = kMinorScaleDefaultLength;
}
return _minorScaleLength;
}
- (UIColor *)indicatorColor {
if (_indicatorColor == nil) {
_indicatorColor = [UIColor colorWithRed:251.0/255.0 green:207.0/255.0 blue:0.0/255.0 alpha:1.0];
}
return _indicatorColor;
}
- (UIColor *)rulerBackgroundColor {
if (_rulerBackgroundColor == nil) {
_rulerBackgroundColor = [UIColor colorWithRed:243.0/255.0 green:243.0/255.0 blue:243.0/255.0 alpha:1.0];;
}
return _rulerBackgroundColor;
}
- (UIColor *)rulerColor {
if (_rulerColor == nil) {
_rulerColor = [UIColor colorWithRed:232.0/255.0 green:232.0/255.0 blue:232.0/255.0 alpha:1.0];
}
return _rulerColor;
}
- (UIColor *)selectedRuleColor {
if (_selectedRuleColor == nil) {
_selectedRuleColor = [UIColor colorWithRed:246.0/255.0 green:193.0/255.0 blue:21.0/255.0 alpha:1.0];
}
return _selectedRuleColor;
}
- (NSInteger)splitNumber {
if (_splitNumber == 0) {
_splitNumber = 3;
}
return _splitNumber;
}
- (CGFloat)scaleWidth {
if (_scaleWidth == 0) {
_scaleWidth = 2;
}
return _scaleWidth;
}
@end
下面是基本的使用,我只是選擇幾個屬性進行設(shè)置,使用姿勢如下:
YCRulerView *rulerView = [[YCRulerView alloc] initWithFrame:rulerFrame];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rulerView.bounds
byRoundingCorners:corner
cornerRadii:CGSizeMake(10.f, 10.f)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = rulerView.bounds;
maskLayer.path = maskPath.CGPath;
rulerView.layer.mask = maskLayer;
rulerView.backgroundColor = rulerBackgroundColor;
// 最大值
rulerView.maxValue = maxValue;
// 設(shè)置默認(rèn)值
rulerView.selectedValue = selectedValue;
// 指示塊的顏色
rulerView.indicatorColor = indicatorColor;
// 刻度尺的顏色
rulerView.rulerColor = rulerColor;
// 選中刻度尺的顏色
rulerView.selectedRuleColor = selectedRulerColor;
[view addSubview:rulerView];
- 第二種實現(xiàn)思路,以上這種實現(xiàn)方法使用UIView實現(xiàn)的,在實現(xiàn)完這一套之后,我覺得不是太好,如果內(nèi)部的刻度線比較多的時候,如果還是采用創(chuàng)建View的方法,就會造成內(nèi)存的極大消耗,因此我在這個基礎(chǔ)之上,又實現(xiàn)了另外一套方法,當(dāng)然這套方法實現(xiàn)的不是很好,還存在很大的問題,先記錄一下,后期完成之后,在更新一下這篇文章。
首先看一下頭文件的部分,頭文件和上面實現(xiàn)方法頭文件差不多:
/** 選中的數(shù)值 */
@property (nonatomic, assign) CGFloat selectedValue;
/** 最小值 */
@property (nonatomic, assign) NSInteger minValue;
/** 最大值 */
@property (nonatomic, assign) NSInteger maxValue;
/** 步長 */
@property (nonatomic, assign) NSInteger valueStep;
/** 小刻度分成幾塊 */
@property (nonatomic, assign) NSInteger splitNumber;
/** 主刻度長度,默認(rèn)值 25.0 */
@property (nonatomic, assign) CGFloat majorScaleLength;
/** 中間刻度長度,默認(rèn)值 20.0 */
@property (nonatomic, assign) CGFloat middleScaleLength;
/** 小刻度長度,默認(rèn)值 10.0 */
@property (nonatomic, assign) CGFloat minorScaleLength;
/** 指示條顏色 */
@property (nonatomic, strong) UIColor *indicatorColor;
/** 背景顏色 */
@property (nonatomic, strong) UIColor *rulerBackgroundColor;
/** 指示條的顏色 */
@property (nonatomic, strong) UIColor *rulerColor;
/** 是否顯示刻度尺上的數(shù)值 */
@property (nonatomic, assign) BOOL showRulerNumber;
.m文件的實現(xiàn)如下:
#import "YCRuler.h"
/** 主刻度長度默認(rèn)值 */
static CGFloat const kMajorScaleDefaultLength = 20.0;
/** 中間刻度長度默認(rèn)值 */
static CGFloat const kMiddleScaleDefaultLength = 20.0;
/** 小刻度長度默認(rèn)值 */
static CGFloat const kMinorScaleDefaultLength = 10.0;
@interface YCRuler ()
/** 小刻度間距 */
@property (nonatomic, assign) CGFloat minorScaleSpacing;
/** 刻度尺 */
@property (nonatomic, strong) UIImageView *rulerImageView;
/** 指示器 */
@property (nonatomic, strong) UIView *indicatorView;
@end
@implementation YCRuler
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupUI];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupUI];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (_rulerImageView.image == nil) {
[self reloadRuler];
}
}
#pragma mark - 設(shè)置屬性
- (void)setSelectedValue:(CGFloat)selectedValue {
if (selectedValue < _minValue || selectedValue > _maxValue || _valueStep <= 0) {
return;
}
_selectedValue = selectedValue;
_indicatorView.backgroundColor = self.indicatorColor;
_indicatorView.alpha = 0.6;
_indicatorView.frame = CGRectMake(0, 0, (self.frame.size.width * self.selectedValue / self.maxValue) , self.frame.size.height);
}
#pragma mark - 繪制標(biāo)尺相關(guān)方法
/**
* 刷新標(biāo)尺
*/
- (void)reloadRuler {
UIImage *image = [self rulerImage];
if (image == nil) {
return;
}
_rulerImageView.image = image;
[_rulerImageView sizeToFit];
_rulerImageView.frame = self.bounds;
// 更新初始值
self.selectedValue = _selectedValue;
}
// 生成標(biāo)尺圖像
- (UIImage *)rulerImage {
// 1. 常數(shù)計算
CGFloat steps = [self stepsWithValue:_maxValue];
if (steps == 0) {
return nil;
}
// 水平方向繪制圖像的大小
CGSize textSize = [self maxValueTextSize];
CGFloat height = self.majorScaleLength + textSize.height + 2 * self.minorScaleSpacing;
CGFloat startX = textSize.width - 5;
CGRect rect = CGRectMake(0, 0, steps * self.minorScaleSpacing + 2 * startX, height);
// 2. 繪制圖像
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
UIBezierPath *path = [UIBezierPath bezierPath];
for (NSInteger i = _minValue; i <= _maxValue; i += _valueStep) {
// 繪制主刻度
CGFloat x = (i - _minValue) / _valueStep * self.minorScaleSpacing * self.splitNumber + startX;
if (i == _minValue) {
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(0, 0)];
} else if (i == _maxValue) {
break;
} else {
[path moveToPoint:CGPointMake(x, height)];
[path addLineToPoint:CGPointMake(x, height - self.majorScaleLength - self.frame.size.height * 2 / 3)];
}
// 繪制小刻度線
for (NSInteger j = 1; j < self.splitNumber; j++) {
CGFloat scaleX = x + j * self.minorScaleSpacing;
CGFloat scaleY = 0;
[path moveToPoint:CGPointMake(scaleX, height)];
if (self.splitNumber == 3) {
scaleY = height - self.minorScaleLength - 10;
} else {
scaleY = height - ((j == self.splitNumber / 2) ? self.middleScaleLength : self.minorScaleLength);
}
[path addLineToPoint:CGPointMake(scaleX, scaleY)];
}
}
[self.rulerBackgroundColor set];
[path stroke];
// 2> 繪制刻度值
NSDictionary *strAttributes = [self scaleTextAttributes];
for (NSInteger i = _minValue; i <= _maxValue; i += _valueStep) {
NSString *str = @(i).description;
if (i == _minValue) {
[str drawInRect:CGRectZero withAttributes:strAttributes];
} else if (i == _maxValue) {
[str drawInRect:CGRectZero withAttributes:strAttributes];
} else {
CGRect strRect = [str boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:strAttributes
context:nil];
strRect.origin.x = (i - _minValue) / _valueStep * self.minorScaleSpacing * self.splitNumber + startX - strRect.size.width * 0.5;
strRect.origin.y = 8;
strRect.size.width = 20;
strRect.size.height = 20;
if (self.showRulerNumber) {
[str drawInRect:strRect withAttributes:strAttributes];
}
}
}
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
// 計算最小值和指定 value 之間的步長,即:繪制刻度的總數(shù)量
- (CGFloat)stepsWithValue:(CGFloat)value {
if (_minValue >= value || _valueStep <= 0) {
return 0;
}
return (value - _minValue) / _valueStep * self.splitNumber;
}
// 以水平繪制方向計算 `最大數(shù)值的文字` 尺寸
- (CGSize)maxValueTextSize {
NSString *scaleText = @(self.maxValue).description;
CGSize size = [scaleText boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:[self scaleTextAttributes]
context:nil].size;
return CGSizeMake(floor(size.width), floor(size.height));
}
// 文本屬性字典
- (NSDictionary *)scaleTextAttributes {
CGFloat fontSize = 10 * [UIScreen mainScreen].scale * 0.8;
return @{NSForegroundColorAttributeName: [UIColor lightGrayColor],
NSFontAttributeName: [UIFont boldSystemFontOfSize:fontSize]};
}
#pragma mark - 設(shè)置界面
- (void)setupUI {
// 標(biāo)尺圖像
_rulerImageView = [[UIImageView alloc] init];
[self addSubview:_rulerImageView];
// 指示器視圖
_indicatorView = [[UIView alloc] init];
[self addSubview:_indicatorView];
}
#pragma mark - 屬性默認(rèn)值
- (CGFloat)minorScaleSpacing {
if (_minorScaleSpacing <= 0) {
_minorScaleSpacing = self.frame.size.width /(self.maxValue * self.splitNumber);
}
return _minorScaleSpacing;
}
- (CGFloat)majorScaleLength {
if (_majorScaleLength <= 0) {
_majorScaleLength = kMajorScaleDefaultLength;
}
return _majorScaleLength;
}
- (CGFloat)middleScaleLength {
if (_middleScaleLength <= 0) {
_middleScaleLength = kMiddleScaleDefaultLength;
}
return _middleScaleLength;
}
- (CGFloat)minorScaleLength {
if (_minorScaleLength <= 0) {
_minorScaleLength = kMinorScaleDefaultLength;
}
return _minorScaleLength;
}
- (UIColor *)indicatorColor {
if (_indicatorColor == nil) {
_indicatorColor = [UIColor orangeColor];
}
return _indicatorColor;
}
- (UIColor *)rulerBackgroundColor {
if (_rulerBackgroundColor == nil) {
_rulerBackgroundColor = [UIColor lightGrayColor];
}
return _rulerBackgroundColor;
}
- (NSInteger)splitNumber {
if (_splitNumber == 0) {
_splitNumber = 3;
}
return _splitNumber;
}
使用姿勢和第一種一樣,但是第二種的靈活度還不是很好,在設(shè)置一個不合法的值的時候,顯示會有問題, 這個我正在排查,等解決之后,我會更新一下這份代碼。