
若覺得前面的廢話太多,大家可直接看
- 3、第三種方法也是這篇博客重點需要講解的方法,用runtime來處理,大致步驟如下幾點
源碼地址:
https://github.com/chenfanfang/CollectionsOfExample

背景
在以前的app項目中,由于懶得封裝加載的蒙版,就直接使用了MBProgressHUD和SVProgressHUD,不得不說用得的確很方便,特別是SVProgressHUD。不過不知大家有沒有發(fā)現(xiàn)這樣顯示蒙版和隱藏蒙版非常突兀嗎?特別是網(wǎng)絡請求非??斓臅r候,可能1s的時間都不到,給人有種一閃的感覺,當然處理方法很多(比如自己寫個方法進行延遲隱藏.....)這里不做過多的闡述。
由于個人實在不喜歡那種一閃的感覺,所以打算在新啟動的項目中自己寫一個提示加載的蒙版,思來想去,最終還是覺得類似微信加載網(wǎng)頁的那種進度條比較符合我的胃口,于是封裝了個簡易的進度條,并且添加到整個項目的基類控制器(百分百AOP主義者不必做過多關于基類與AOP的評論,本文主要進行runtime監(jiān)聽方法調(diào)用的實踐)。
老樣子先附上效果圖一張

下面先附上簡單的代碼(由于部分用到宏,為了直觀看到效果圖,大家可以下載源碼查看)
封裝的進度條.h文件
#import <UIKit/UIKit.h>
/**
* 進度條
*/
@interface FFProgressView : UIView
- (void)star;
- (void)end;
@end
封裝的進度條.m文件
#import "FFProgressView.h"
@interface FFProgressView ()
/** 前景view */
@property (nonatomic, strong) UIView *foregroundView;
@end
@implementation FFProgressView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
//背景色
self.backgroundColor = [UIColor clearColor];
self.foregroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 3)];
//前景色
self.foregroundView.backgroundColor = FFColor(54, 188, 37);
[self addSubview:self.foregroundView];
}
return self;
}
/// 開始加載進度條
- (void)star {
self.foregroundView.width = 0;
self.hidden = NO;
[UIView animateWithDuration:10 delay:0 usingSpringWithDamping:1.0f initialSpringVelocity:1.0f options:kNilOptions animations:^{
self.foregroundView.width = FFScreenW * 0.8;
} completion:nil];
}
/// 結束加載進度條
- (void)end {
[UIView animateWithDuration:0.5 animations:^{
self.foregroundView.width = FFScreenW;
} completion:^(BOOL finished) {
self.hidden = YES;
}];
}
基類控制器的.h文件
#import <UIKit/UIKit.h>
/**
* 基類控制器
*/
@interface FFRuntimeBaseController : UIViewController
/** 啟動進度條 */
- (void)starProgress;
/** 結束進度條 */
- (void)endProgress;
@end
基類控制器的.m文件
#import "FFRuntimeBaseController.h"
//view
#import "FFProgressView.h"
@interface FFRuntimeBaseController ()
/** 網(wǎng)絡請求的進度條 */
@property (nonatomic, strong) FFProgressView *progressView;
@end
@implementation FFRuntimeBaseController
- (void)viewDidLoad {
[super viewDidLoad];
}
//=================================================================
// 懶加載
//=================================================================
#pragma mark - 懶加載
- (FFProgressView *)progressView {
if (_progressView == nil) {
_progressView = [FFProgressView new];
[self.view addSubview:_progressView];
_progressView.frame = CGRectMake(0, 0, FFScreenW, 3);
_progressView.hidden = YES;
}
return _progressView;
}
//=================================================================
// 進度條
//=================================================================
#pragma mark - 進度條
- (void)starProgress {
[self.progressView star];
}
- (void)endProgress {
[self.progressView end];
}
@end
在子類控制器中使用進度條
- (void)viewDidLoad {
[super viewDidLoad];
//網(wǎng)絡請求前開始加載進度條
[self starProgress];
//模擬網(wǎng)絡延遲
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//網(wǎng)絡請求完成結束進度條的加載
[self endProgress];
});
}
遇到問題
怎樣保持進度條始終在控制器view的最前面呢?(若不做處理,可能進度條會在控制器的view addsubView的時候給遮擋住) 我的第一反應是用KVO監(jiān)聽做處理,最終以失敗告終(若簡書朋友發(fā)現(xiàn)用KVO可以處理的話,麻煩留下處理的方法)。
解決方案
想了三種解決方案,并且列出了可行性。
- 1、用UIWindow來處理進度條,這樣進度條就永遠不會被遮擋。
可行性分析:若整個項目中只用一個進度條就可以用這個方式解決(什么叫只用一個進度條呢?就是一個頁面若沒有加載完成進入不了別的界面。舉個例子,兩個界面同時處于加載狀態(tài),若共用一個進度條則不合理。)
- 2、在基類控制器重寫
-(void)loadView方法,自定義self.view即可,簡易步驟如下
- (void)loadView {
self.view = [[FFCustomView alloc] init];
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",[self.view class]);
}
//控制器輸出為 :FFCustomView
這樣子就很容易監(jiān)聽到addSubView了,在FFCustomView的.h定義一個回調(diào)block,在FFCustomView的.m重寫addSubview,簡易代碼如下
FFCustomView.h
#import <UIKit/UIKit.h>
@interface FFCustomView : UIView
/** addSubView完成的回調(diào)block */
@property (nonatomic, copy) void(^didAddSubView)();
@end
FFCustomView.m
#import "FFCustomView.h"
@implementation FFCustomView
- (void)addSubview:(UIView *)view {
[super addSubview:view];
if (self.didAddSubView) {
self.didAddSubView();
}
}
@end
在控制器中監(jiān)聽addSubView簡易代碼如下
FFCustomView *customView = (FFCustomView *)self.view;
customView.didAddSubView = ^ {
NSLog(@"addSubView啦");
//addSubView之后需要做何處理的代碼寫在這即可
};
[self.view addSubview:[UIView new]];
//控制器輸出:addSubView啦
可行性分析:在解決監(jiān)聽控制器的view 添加子控件的情景下是可行的。這種方法只是作為一種普通的解決方案,本文主要介紹如何用runtime監(jiān)聽所有方法調(diào)用。所以大家重點請看第三點解決方案。
- 3、第三種方法也是這篇博客重點需要講解的方法,用runtime來處理,大致步驟如下幾點
- 新建一個UIView的category
- 通過runtime關聯(lián)的方式添加屬性 objc_setAssociatedObject、objc_getAssociatedObject,添加一個addSubview之后的回調(diào)block的屬性:void(^didAddsubView)()
- 通過runtime進行方法的交換,監(jiān)聽addSubView
下面直接附上代碼
category的.h文件
#import <UIKit/UIKit.h>
@interface UIView (FFExtension)
/** addSubview之后的回調(diào)block */
@property (nonatomic, copy) void(^didAddsubView)();
@end
category的.m文件
#import "UIView+FFExtension.h"
#import <objc/runtime.h>
@implementation UIView (FFExtension)
/// 添加屬性
// 定義關聯(lián)的key
static const char *key_didAddsubView = "didAddsubView";
- (void (^)())didAddsubView {
return objc_getAssociatedObject(self, key_didAddsubView);
}
- (void)setDidAddsubView:(void (^)())didAddsubView {
// 第一個參數(shù):給哪個對象添加關聯(lián)
// 第二個參數(shù):關聯(lián)的key,通過這個key獲取
// 第三個參數(shù):關聯(lián)的value
// 第四個參數(shù):關聯(lián)的策略
objc_setAssociatedObject(self, key_didAddsubView, didAddsubView, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
/// 黑魔法交換方法
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//進行方法交換,目的:讓UIView addSubView的時候可以被監(jiān)聽到
Class class_UIView = NSClassFromString(@"UIView");
SEL originalSelector = @selector(addSubview:);
SEL swizzledSelector = @selector(swizzlingAddSubview:);
Method originalMethod = class_getInstanceMethod(class_UIView, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class_UIView, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class_UIView,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class_UIView,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)swizzlingAddSubview:(UIView *)view {
[self swizzlingAddSubview:view];
if (self.didAddsubView) {
self.didAddsubView();
}
}
@end
這樣就可以監(jiān)聽所有的view的addSubview了,監(jiān)聽方式如下
- (void)viewDidLoad {
[super viewDidLoad];
self.view.didAddsubView = ^ {
NSLog(@"我是控制器的view,我添加了一個新控件");
};
[self.view addSubview:[UIView new]];
}
//控制臺輸出:我是控制器的view,我添加了一個新控件
可行性分析:個人是比較推薦用這種方法進行處理。按理說,要監(jiān)聽iOS中方法的調(diào)用,用此方法即可實現(xiàn)。萬能KVO.......
解決上面進度條可能被其他view遮擋的問題
將原本進度條的懶加載改成如下即可
//=================================================================
// 懶加載
//=================================================================
#pragma mark - 懶加載
- (FFProgressView *)progressView {
if (_progressView == nil) {
_progressView = [FFProgressView new];
[self.view addSubview:_progressView];
_progressView.frame = CGRectMake(0, 0, FFScreenW, 3);
_progressView.hidden = YES;
//和前面的懶加載相比,主要添加了如下的代碼
__weak typeof(self) weakSelf = self;
__weak typeof(_progressView) weakProgressView = _progressView;
self.view.didAddsubView = ^ {
//將進度條挪到最前面
[weakSelf.view bringSubviewToFront:weakProgressView];
};
}
return _progressView;
}
總結
按理說,利用方式3的方式可以監(jiān)聽到所有方法的調(diào)用,這樣也就補充了KVO的不足之處。
博客中若有什么錯誤或者不足,還望指出