简单使用
网络通讯时,显示小转转;
封装:
// 展示转圈,用户不能操作
+ (void)show:(nullable NSString*)message {
[SVProgressHUD setDefaultStyle:SVProgressHUDStyleDark];
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
[SVProgressHUD showWithStatus:message];
}
// 取消转圈
+ (void)dismiss {
[SVProgressHUD dismiss];
}
// 模仿安卓的toast消息
+ (void)toast:(nullable NSString*)message {
[SVProgressHUD setMinimumDismissTimeInterval:2];
[SVProgressHUD setMaximumDismissTimeInterval:5];
[SVProgressHUD setDefaultStyle:SVProgressHUDStyleDark];
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone];
[SVProgressHUD showImage:[UIImage imageNamed:@"not exist"] status:message];
}
调用代码,一般用在网络访问中:
[KJTHud show:@"加载中...."];
[KJTNetwork postWithUrl:url parameters:parameters success:^(id _Nullable response) {
[KJTHud dismiss];
// 网络成功代码
....
} failure:^(NSString * _Nullable message) {
[KJTHud dismiss];
// 网络失败代码
....
// toast显示一下错误消息
[KJTHud toast:message];
}];
实际使用下来,这个第三方库还是挺好用的,接口比较友好,也很简单方便。
遇到的问题
用下来一直挺好的,不过,有天,同事发现了一个问题。就是上面的加载中...
和提示的toast
消息混在了一起。
目前,只在iOS10.3.3
版本的一个iPhone7 Plus
手机上出现,自己用模拟器,其他手机版本上没有重现,概率不高。
源码分析
- 显示loading:
- (void)showProgress:(float)progress status:(NSString*)status {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
if(strongSelf.fadeOutTimer) {
strongSelf.activityCount = 0;
}
// Stop timer
strongSelf.fadeOutTimer = nil;
strongSelf.graceTimer = nil;
// Update / Check view hierarchy to ensure the HUD is visible
[strongSelf updateViewHierarchy];
// Reset imageView and fadeout timer if an image is currently displayed
strongSelf.imageView.hidden = YES;
strongSelf.imageView.image = nil;
// Update text and set progress to the given value
strongSelf.statusLabel.hidden = status.length == 0;
strongSelf.statusLabel.text = status;
strongSelf.progress = progress;
// Choose the "right" indicator depending on the progress
if(progress >= 0) {
// Cancel the indefiniteAnimatedView, then show the ringLayer
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// Add ring to HUD
if(!strongSelf.ringView.superview){
[strongSelf.hudView.contentView addSubview:strongSelf.ringView];
}
if(!strongSelf.backgroundRingView.superview){
[strongSelf.hudView.contentView addSubview:strongSelf.backgroundRingView];
}
// Set progress animated
[CATransaction begin];
[CATransaction setDisableActions:YES];
strongSelf.ringView.strokeEnd = progress;
[CATransaction commit];
// Update the activity count
if(progress == 0) {
strongSelf.activityCount++;
}
} else {
// Cancel the ringLayer animation, then show the indefiniteAnimatedView
[strongSelf cancelRingLayerAnimation];
// Add indefiniteAnimatedView to HUD
[strongSelf.hudView.contentView addSubview:strongSelf.indefiniteAnimatedView];
if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
[(id)strongSelf.indefiniteAnimatedView startAnimating];
}
// Update the activity count
strongSelf.activityCount++;
}
// Fade in delayed if a grace time is set
if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) {
strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes];
} else {
[strongSelf fadeIn:nil];
}
// Tell the Haptics Generator to prepare for feedback, which may come soon
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
if (@available(iOS 10.0, *)) {
[strongSelf.hapticGenerator prepare];
}
#endif
}
}];
}
文字由属性strongSelf.statusLabel
来展示。
- 显示toast:
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// Stop timer
strongSelf.fadeOutTimer = nil;
strongSelf.graceTimer = nil;
// Update / Check view hierarchy to ensure the HUD is visible
[strongSelf updateViewHierarchy];
// Reset progress and cancel any running animation
strongSelf.progress = SVProgressHUDUndefinedProgress;
[strongSelf cancelRingLayerAnimation];
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// Update imageView
if (self.shouldTintImages) {
if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
strongSelf.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
strongSelf.imageView.tintColor = strongSelf.foregroundColorForStyle;;
} else {
strongSelf.imageView.image = image;
}
strongSelf.imageView.hidden = NO;
// Update text
strongSelf.statusLabel.hidden = status.length == 0;
strongSelf.statusLabel.text = status;
// Fade in delayed if a grace time is set
// An image will be dismissed automatically. Thus pass the duration as userInfo.
if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) {
strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:@(duration) repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes];
} else {
[strongSelf fadeIn:@(duration)];
}
}
}];
}
文字由属性strongSelf.statusLabel
来展示。
- 属性的getter函数:
- (UILabel*)statusLabel {
if(!_statusLabel) {
_statusLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_statusLabel.backgroundColor = [UIColor clearColor];
_statusLabel.adjustsFontSizeToFitWidth = YES;
_statusLabel.textAlignment = NSTextAlignmentCenter;
_statusLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
_statusLabel.numberOfLines = 0;
}
if(!_statusLabel.superview) {
[self.hudView.contentView addSubview:_statusLabel];
}
// Update styling
_statusLabel.textColor = self.foregroundColorForStyle;
_statusLabel.font = self.font;
return _statusLabel;
}
- 理论上讲,文字都是由属性
strongSelf.statusLabel
来展示,只有一个,应该不会出现这种情况才对。具体什么原因,从源码上看不出来。一个猜测就是由于某种原因,一个UILabel
添加之后,丢失了,导致又添加了一个新的UILabel
,从而出现一个属性,但是两个UILabel
同时出现的问题。
Dissmiss函数的问题
- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// Stop timer
strongSelf.graceTimer = nil;
// Post notification to inform user
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification
object:nil
userInfo:[strongSelf notificationUserInfo]];
// Reset activity count
strongSelf.activityCount = 0;
__block void (^animationsBlock)(void) = ^{
// Shrink HUD a little to make a nice disappear animation
strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f);
// Fade out all effects (colors, blur, etc.)
[strongSelf fadeOutEffects];
};
__block void (^completionBlock)(void) = ^{
// Check if we really achieved to dismiss the HUD (<=> alpha values are applied)
// and the change of these values has not been cancelled in between e.g. due to a new show
if(self.backgroundView.alpha == 0.0f){
// Clean up view hierarchy (overlays)
[strongSelf.controlView removeFromSuperview];
[strongSelf.backgroundView removeFromSuperview];
[strongSelf.hudView removeFromSuperview];
[strongSelf removeFromSuperview];
// Reset progress and cancel any running animation
strongSelf.progress = SVProgressHUDUndefinedProgress;
[strongSelf cancelRingLayerAnimation];
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// Remove observer <=> we do not have to handle orientation changes etc.
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf];
// Post notification to inform user
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification
object:strongSelf
userInfo:[strongSelf notificationUserInfo]];
// Tell the rootViewController to update the StatusBar appearance
#if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS
UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController;
[rootController setNeedsStatusBarAppearanceUpdate];
#endif
// Run an (optional) completionHandler
if (completion) {
completion();
}
}
};
// UIViewAnimationOptionBeginFromCurrentState AND a delay doesn't always work as expected
// When UIViewAnimationOptionBeginFromCurrentState is set, animateWithDuration: evaluates the current
// values to check if an animation is necessary. The evaluation happens at function call time and not
// after the delay => the animation is sometimes skipped. Therefore we delay using dispatch_after.
dispatch_time_t dipatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
dispatch_after(dipatchTime, dispatch_get_main_queue(), ^{
if (strongSelf.fadeOutAnimationDuration > 0) {
// Animate appearance
[UIView animateWithDuration:strongSelf.fadeOutAnimationDuration
delay:0
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
animationsBlock();
} completion:^(BOOL finished) {
completionBlock();
}];
} else {
animationsBlock();
completionBlock();
}
});
// Inform iOS to redraw the view hierarchy
[strongSelf setNeedsDisplay];
}
}];
}
[strongSelf.hudView removeFromSuperview]; 这里的hudView
是上面出问题的statusLabel
的父view
。把父view
移除了,可是作为子view
的statusLabel
却没有移除,这就有了不一致的潜在隐患。
建议:添加[strongSelf. statusLabel removeFromSuperview];
这么一句,将父子视图都移除一遍。
其实,这里既然用了一个单例+ (SVProgressHUD*)sharedView
,那么那些初始化工作就应该在单例初始化的时候一并做了,其他时候只要控制显示隐藏就可以了。过多的添加移除视图,容易带来意想不到的问题。
修改
这是第三方库,并且用CocoaPod
进行管理,不方便修改。所以,打算用另一个第三方库MBProgressHUD来代替这个库。