最近公司项目需求需要做一个网络测试的小功能,在网上查了很多资料,关于网络测速差到了两种方式:
(参考: [Joy___]https://www.jianshu.com/u/9c51a213b02e)
方案1:通过上传和下载数据包,使用 TotalSize / TotalTime 来计算真实的上传和下载速率是多少。
方案2:通过读取网卡数据来计算,读取上一秒的整体流量消耗 T1,然后读取当前的流量消耗 T2,那么 T2 - T1 其实可以表示为当前的一个网速情况。同时这个流量数据是可以区分蜂窝网络、Wi-Fi的,也可以区分哪些是上行流量,那些是下行流量。
两种方案各有优劣,可以在合适的场合来选择对应的方案
第一种方案感觉是比较准确,这个时候是真实的在下载或上传数据,比较充分的利用了当前的带宽,计算的网速也比较接近真实的网速值。但是蜂窝网络下,会消耗用户的少量流量。
第二种方案在下载和上传东西时,计算的值和第一种方案比较接近。但是如果当前系统内没有 App 在被使用,处于静止状态的话,其实当前读取的流量值是比较小的,无法反映出网速情况,但是可以实时反映流量消耗状况。
最终我选择的是第一种方式去做,我个人理解的是想要进行网络测速就一定要有流量的消耗,若是采用第二种方式会造成测速不准确,第一种虽然会造成用户在流量方面有一定的损耗,但是考虑到目前公司对于网络测速的应用环境所以最佳的方案就是第一种。
关于具体怎么测速就不多说,我参考了@Joy___的代码。
先上效果图:
效果图中显示的指针滑过的部分和未滑过的部分有颜色的区分,所以我采用了CAShapeLayer自己绘制的表盘:
UIBezierPath *outArc=[UIBezierPath bezierPathWithArcCenter:LuCenter radius:self.arcRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
CAShapeLayer* shapeLayer=[CAShapeLayer layer];
shapeLayer.lineWidth=lineWitdth;
shapeLayer.fillColor=filleColor.CGColor;
shapeLayer.strokeColor=strokeColor.CGColor;
shapeLayer.path=outArc.CGPath;
shapeLayer.lineCap=kCALineCapRound;
[self.layer addSublayer:shapeLayer];
绘制最外圈的弧线,起始角度我设置的是-M_PI_4*5,结束角度设置的为M_PI_4
CGFloat perAngle=self.arcAngle/divide;
for (NSInteger i = 0; i<= divide; i++) {
//我们需要计算出每段弧线的起始角度和结束角度
CGFloat startAngel = (self.startAngle+ perAngle * i);
CGFloat endAngel = startAngel + perAngle/5;
UIBezierPath *tickPath = [UIBezierPath bezierPathWithArcCenter:LuCenter radius:self.scaleRadius startAngle:startAngel endAngle:endAngel clockwise:YES];
CAShapeLayer *perLayer = [CAShapeLayer layer];
perLayer.fillColor = [UIColor clearColor].CGColor;
if((remainder!=0)&&(i % remainder) == 0) {
perLayer.strokeColor = strokeColor.CGColor;
perLayer.lineWidth = scaleLineBigWidth;
}else{
perLayer.strokeColor = strokeColor.CGColor;;
perLayer.lineWidth = scaleLineNormalWidth;
}
perLayer.path = tickPath.CGPath;
[self.layer addSublayer:perLayer];
}
画刻度线,我画了100个刻度线,具体什么情况可以自己去设置
/**
* 画刻度值,逆时针设定label的值,将整个仪表切分为N份,每次递增仪表盘弧度的N分之1
*
* @param divide 刻度值几等分
*/
-(void)DrawScaleValueWithDivide:(NSInteger)divide{
CGFloat textAngel =self.arcAngle/divide;
if (divide==0) {
return;
}
for (NSUInteger i = 0; i <= divide; i++) {
CGPoint point = [self calculateTextPositonWithArcCenter:LuCenter Angle:-(self.endAngle-textAngel*i)];
NSString *tickText = [NSString stringWithFormat:@"%ld",(divide - i)*10/divide];
//默认label的大小23 * 14
UILabel *text = [[UILabel alloc] initWithFrame:CGRectMake(point.x - 8, point.y - 7, 30, 14)];
text.text = tickText;
text.font = [UIFont systemFontOfSize:14.f];
text.textColor = RGBA(255, 255, 255, 0.33);
text.textAlignment = NSTextAlignmentLeft;
[self.textArray addObject:text];
[self addSubview:text];
}
}
//默认计算半径-10,计算label的坐标
- (CGPoint)calculateTextPositonWithArcCenter:(CGPoint)center Angle:(CGFloat)angel {
CGFloat x = (self.scaleValueRadius - 15)* cosf(angel);
CGFloat y = (self.scaleValueRadius - 15)* sinf(angel);
return CGPointMake(center.x + x, center.y - y);
}
画刻度值,我采用的是计算出每一个刻度值的位置,然后添加label
- (void)refreshDashboard:(CGFloat)currentValue {
// 控制范围
if (currentValue > self.maxValue) {
currentValue = self.maxValue;
}
if (currentValue <= self.minValue) {
currentValue = self.minValue;
}
//移除上一次的layer,否则不会出现进度条变化的效果
if (self.progressLayer) {
[self.progressLayer removeFromSuperlayer];
}
if (self.insideProgressLayer) {
[self.insideProgressLayer removeFromSuperlayer];
}
if (self.layerArray.count > 0) {
for (CAShapeLayer *layer in self.layerArray) {
[layer removeFromSuperlayer];
}
[self.layerArray removeAllObjects];
}
// 百分比
CGFloat percent = (currentValue - self.minValue) / (self.maxValue - self.minValue);
// 当前角度
CGFloat currentAngle = self.startAngle + (fabs(self.endAngle - self.startAngle) * percent);
//计算当前指针转动的角度
CGFloat imageCurrentAngle = M_PI_4*5+(M_PI*3/2 * percent);
// 前景弧形绘制
[self dashboardDrawPercent:percent startAngle:self.startAngle endAngle:currentAngle imageCurrentAngle:imageCurrentAngle currentValue:currentValue];
}
为了实现指针转动是滑过的部分和未滑过的部分刻度和刻度值颜色不同,我声明了两个属性
//存放刻度layer
@property (nonatomic, strong) NSMutableArray *layerArray;
//存放刻度值label
@property (nonatomic, strong) NSMutableArray *textArray;
刷新进度的时候会走一个循环,在我创建的layerArray中存放的是当前进度下创建出来的刻度,因此我们只需要将这个数组中的layer都从Superlayer中移除就可以了并且清空数组
if (self.layerArray.count > 0) {
for (CAShapeLayer *layer in self.layerArray) {
[layer removeFromSuperlayer];
}
[self.layerArray removeAllObjects];
}
实际上刻度值颜色的变化和刻度颜色的变化同理,根据当前的值和textArray中的label.text去做对比
for (UILabel *textLabel in self.textArray) {
if (currentValue > [textLabel.text floatValue]) {
textLabel.textColor = [UIColor whiteColor];
} else {
textLabel.textColor = RGBA(255, 255, 255, 0.33);
}
}
这样基本上的核心部分就完成了,还有一个指针的旋转,我使用的时候一张指针图片
- (UIImageView *)innerCursorImageView {
if (!_innerCursorImageView) {
UIImageView *innerCursorImageView = [[UIImageView alloc] init];
innerCursorImageView.image = [UIImage imageNamed:@"指针"];
innerCursorImageView.frame = CGRectMake(self.bounds.size.width / 2 - 10, Calculate_radius - 125, innerCursorImageView.image.size.width * 0.5, innerCursorImageView.image.size.height * 0.5);
// 旋转成和仪表盘角度一致
[self setAnchorPoint:CGPointMake(0.5, 0.816993) forView:innerCursorImageView];//因为layer位置会发生变化,需要重新设置
innerCursorImageView.transform = CGAffineTransformMakeRotation(M_PI_4*5);//图片绕某一点旋转
_innerCursorImageView = innerCursorImageView;
}
return _innerCursorImageView;
}
这里遇到了一个坑,开始的时候我直接设置imageView.transform,发现是围绕imageView的中心的去旋转的,时间指针转动的时候围绕的是指针偏下部的一个点,所以需要设置一下,调用- (void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view这个方法:
- (void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view {
CGPoint oldOrigin = view.frame.origin;
//anchorPoint(锚点):以自己的左上角为原点(0, 0)
//它的x、y取值范围都是0~1,默认值为(0.5, 0.5)
view.layer.anchorPoint = anchorPoint;
CGPoint newOrigin = view.frame.origin;
CGPoint transition;
transition.x = newOrigin.x - oldOrigin.x;
transition.y = newOrigin.y - oldOrigin.y;
view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
}
imageView转动的起始点是M_PI_45 结束点是M_PI_43
实际上当角度为M_PI时,指针指向的正好是时钟的6点的位置根据我们的设计图来看得出的起始点和结束点,然后再每一次刷新时去计算新的结束点。
Demo:https://github.com/rongwf/NetworkSpeed