导语
在一个阳光明媚的午后,组织终于决定把公司的SDK产品,由Native更换成H5,你没看错,就是用Native界面做的SDK,说多了都是眼泪。产品说,网页加载的时候要有进度条,OK,没问题,一个字就是“干”,你懂的。
现在的iOS 应用中,或多或少都会有H5页,因为H5有Native所不具备的灵活性,比如应用中的活动展示,需要不定时的更新,使用H5来做就能轻松搞定!iOS中常用的H5容器有两种:1、 UIWebView 2、WKWebView。
-
WKWebView 是iOS8.0以后开放的API,可以通过KVO来监听
estimatedProgress
属性的变化来获取当前网页的加载进度,如果你的项目不用适配iOS6、iOS7(太爽了,幸福感爆棚有木有),那么你可以很easy的做一个WebView的进度条。 - UIWebView 如果你的应用需要适配iOS7、或者iOS6 或者更早😂。UIWebView是你不二的(也是唯一的)选择,查看系统的API后并没有发现可以获取加载进度的途径,so...你看到的网页有加载进度,如果它是用UIWebView加载的,那进度条一定是 假的! 假的!假的!😁
应商户要求,SDK最低需要支持iOS6.0,然而组织并没有6.0系统的测试机,领导决定,把SDK最低支持的系统为设为6.0,虽然没有真机试过,到底能不能在6.0系统上使用也未知,鉴于这些,H5只能通过UIWebView来加载了。
废话不多说,先来个成品的效果图:
效果1 图片1
效果2 图片2
1、第一次尝试
进度条嘛,第一反应就是用系统的UIProgressView
,然而试过之后才发现系统的UIProgressView
限制太多,可以自定义的太少了,所以就放弃了。
2、第二次尝试
百度、Google一番,好多道友都建议使用CAShapeLayer来做,好吧,干!
查看系统CAShapeLayer的API发现,CAShapeLayer有两个属性
/* These values define the subregion of the path used to draw the
* stroked outline. The values must be in the range [0,1] with zero
* representing the start of the path and one the end. Values in
* between zero and one are interpolated linearly along the path
* length. strokeStart defaults to zero and strokeEnd to one. Both are
* animatable. */
@property CGFloat strokeStart;
@property CGFloat strokeEnd;
意思就是CAShapeLayer的设置这两个描边的起始和结束位置是有动画效果的
自定一个类DKProgressLayer继承自CAShapeLayer
#define DEVICE_WIDTH [UIScreen mainScreen].bounds.size.width
@interface DKProgressLayer : CAShapeLayer
@property (nonatomic, strong) UIColor *progressColor;
/**
进度条开始加载
*/
- (void)progressAnimationStart;
/**
进度条加载完成
*/
- (void)progressAnimationCompletion;
@end
@interface DKProgressLayer ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) CGFloat stepWidth;
@end
static NSTimeInterval const progressInterval = 0.01;
@implementation DKProgressLayer
- (instancetype)init {
if (self = [super init]) {
self.progressColor = [UIColor whiteColor];
self.stepWidth = 0.01;
self.lineWidth = 2;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 2)];
[path addLineToPoint:CGPointMake(DEVICE_WIDTH, 2)];
self.path = path.CGPath;
self.strokeEnd = 0;
}
return self;
}
- (void)setProgressColor:(UIColor *)progressColor {
if (!progressColor) {
return;
}
_progressColor = progressColor;
self.progressColor = progressColor;
}
/* 不断设置layer描边的结束位置 */
- (void)progressChanged:(NSTimer *)timer {
self.strokeEnd += _stepWidth;
if (self.strokeEnd > 0.9) {
_stepWidth = 0.0001;
}
}
}
- (void)progressAnimationStart {
self.hidden = NO;
if (_timer) {
[self invalidateTimer];
}
_timer = [NSTimer scheduledTimerWithTimeInterval:progressInterval target:self selector:@selector(progressChanged:) userInfo:nil repeats:YES];
}
- (void)progressAnimationCompletion {
[self invalidateTimer];
self.strokeEnd = 1.0;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.hidden = YES;
_stepWidth = 0.01;
self.strokeEnd = 0;
});
}
- (void)invalidateTimer {
[_timer invalidate];
_timer = nil;
}
@end
好吧,效果来了:
图片3
So easy too Happy,拿给产品去看……
产品:这也太low了吧
猿:握草,微信的效果就是这样好不好,丝般顺滑。
产品:不行,太大众化了,改成一边加载一边渐变的,前边加载,后边渐隐的
猿:尼玛……那不就是QQ的加载效果么,那就不low了吗?
产品:我就要那样的效果,你就告诉我能不能做?
猿:你大爷……
3、第三次尝试
继续百度、Google,并没有发现很好的思路,快要绝望的时候发现CAShapeLayer有一个子类CAGradientLayer
/* The array of CGColorRef objects defining the color of each gradient
* stop. Defaults to nil. Animatable. */
@property(nullable, copy) NSArray *colors;
/* An optional array of NSNumber objects defining the location of each
* gradient stop as a value in the range [0,1]. The values must be
* monotonically increasing. If a nil array is given, the stops are
* assumed to spread uniformly across the [0,1] range. When rendered,
* the colors are mapped to the output colorspace before being
* interpolated. Defaults to nil. Animatable. */
@property(nullable, copy) NSArray<NSNumber *> *locations;
/* The start and end points of the gradient when drawn into the layer's
* coordinate space. The start point corresponds to the first gradient
* stop, the end point to the last gradient stop. Both points are
* defined in a unit coordinate space that is then mapped to the
* layer's bounds rectangle when drawn. (I.e. [0,0] is the bottom-left
* corner of the layer, [1,1] is the top-right corner.) The default values
* are [.5,0] and [.5,1] respectively. Both are animatable. */
@property CGPoint startPoint;
@property CGPoint endPoint;
看注释,貌似能设置起始点、终点,还能设置多种颜色,还能设置颜色的位置,我凑,这不就能满足产品🐶的需求了吗? 快试试……此处省略过程,直接贴代码了
- (void)setProgressStyle:(DKProgressStyle)progressStyle {
_progressStyle = progressStyle;
if (progressStyle == DKProgressStyle_Gradual) {
self.strokeColor = nil;
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
CGFloat RGB[3];
[self getRGBComponents:RGB forColor:_progressColor];
gradientLayer.colors = @[(__bridge id)[UIColor colorWithRed:RGB[0] green:RGB[1] blue:RGB[2] alpha:0.2].CGColor, (__bridge id)_progressColor.CGColor];
gradientLayer.locations = @[@(0), @(0)];
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1.0, 0);
gradientLayer.frame = CGRectMake(0, 0, 0, 2);
_gradientLayer = gradientLayer;
[self addSublayer:gradientLayer];
}
}
- (void)progressChanged:(NSTimer *)timer {
self.strokeEnd += _stepWidth;
/* 超过90% 减缓进度条增长速度 */
if (self.strokeEnd > 0.9) {
_stepWidth = 0.0001;
}
if (_progressStyle == DKProgressStyle_Gradual) {
/* 不断改变layer颜色的起始位置 */
_gradientLayer.locations = @[@(self.strokeEnd/2), @(self.strokeEnd)];
/* 不断改变layer的frame */
_gradientLayer.frame = CGRectMake(0, 0, DEVICE_WIDTH*self.strokeEnd, 2);
}
}
/* 获取颜色的RGB值 */
- (void)getRGBComponents:(CGFloat [3])components forColor:(UIColor *)color {
if (!color) {
components[0] = 1;
components[1] = 1;
components[2] = 1;
return;
}
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char resultingPixel[4];
CGContextRef context = CGBitmapContextCreate(&resultingPixel, 1, 1, 8, 4, rgbColorSpace, kCGImageAlphaNoneSkipLast);
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
CGContextRelease(context);
CGColorSpaceRelease(rgbColorSpace);
for (int component = 0; component < 3; component++) {
components[component] = resultingPixel[component] / 255.0f;
}
}
效果如下:
图片
产品:这么简单的一个东西,你弄这么久,先这么着吧,有什么想法再找你
猿:我日尼玛,傻吊……
好吧,这个需求就暂时告一段落了,产品需求来了再改吧😂
本文github链接,如果你觉得能帮到你,路过给个Star哈。
网友评论
if (!progressColor) {
return;
}
_progressColor = progressColor;
self.progressColor = progressColor;
}
老铁 确定这样不会死循环 哈哈