疑問?
在開發(fā)中我們經常需要用到的組件,自然就是錯誤頁了。一般而言,對于網絡錯誤,數據為空等狀態(tài),我們都會在當前的頁面中展示一張錯誤頁來提示用戶,類似這樣:

常用寫法
要實現這個需求其實并不難,大概分為以下幾種。
- 直接生成
1. 自定義一個錯誤頁的,就像這樣:
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, HPErrorType) {
HPErrorTypeUnavailableNetwork,
HPErrorTypeEmptyData,
HPErrorTypeDefault = HPErrorTypeUnavailableNetwork
};
@interface HPErrorView : UIView
/**
當前錯誤類型【默認為Default】
*/
@property (nonatomic, assign) HPErrorType errorType;
/**
要顯示的文案【為nil使用默認文案】
*/
@property (nonatomic, copy) NSString *customText;
/**
實例化
*/
+(instancetype)viewWithType:(HPErrorType)type;
@end
2. 然后在需要的頁面中直接控制添加和移除:
2.1. 顯示
#import "HPErrorView.h"
static const NSUInteger kErrorViewTag = 1024;
...
@implementation ViewController
/**
顯示錯誤頁
*/
- (void)directShowErrorView {
dispatch_main_async_safe(^{
HPErrorView *errorView = [self checkErrorViewExist];
if (!errorView) {
errorView = [HPErrorView viewWithType:HPErrorTypeDefault]; // 創(chuàng)建
errorView.tag = kErrorViewTag;
[self.view addSubview:errorView]; // 添加
// 布局
errorView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:errorView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:errorView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:errorView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:errorView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0]];
}
[self.view bringSubviewToFront:errorView];
});
}
@end
2.2. 移除
/**
移除錯誤頁
*/
- (void)directShowErrorView {
dispatch_main_async_safe(^{
HPErrorView *errorView = [self checkErrorViewExist]; // 檢查是否存在
if (errorView) {
[errorView removeFromSuperview]; // 移除
}
});
}
2.3. 檢測是否存在
/**
檢測當前頁面是否存在ErrorView
@return ErrorView
*/
- (HPErrorView *)checkErrorViewExist {
return [self.view viewWithTag:kErrorViewTag];
}
2.4. 方法調用
- (void)changErrorViewStatus:(id)sender {
if ([self.item.title isEqualToString:@"展示"]) {
self.item.title = @"隱藏";
// 1. 直接顯示
[self directShowErrorView];
} else {
self.item.title = @"展示";
// 1. 直接隱藏
[self directRemoveErrorView];
}
}
- 使用分類控制
使用分類進行控制的原理其實跟“直接調用”方式是差不多的,只是將這部分代碼放入分類中實現代碼復用。代碼結構如下:
/**
錯誤頁的tag
*/
extern NSUInteger const HPErrorViewTag;
@class HPErrorView;
@interface UIViewController (HPAdd)
/**
顯示錯誤頁
*/
- (void)showErrorView;
/**
移除錯誤頁
*/
- (void)removeErrorView;
/**
檢測當前頁面是否存在ErrorView
@return ErrorView
*/
- (HPErrorView *)checkErrorViewExist;
@end
內部方法實現,方法調用方式均與直接調用方式時一樣,不再贅述。詳情參見附件Demo。
自動管理
以上方法適應于大部分場景,但是還是有一些問題,那就是需要自動調用,對于某些特定的場合(比如說數據為空),是否更簡單的方式呢?答案是一定的。
?考慮到實際使用場景中,展示信息多是使用UITableView或UICollectionView組件,接下來就已UITableView組件為例。
?聯想實際使用,UITableView與UICollectionView中的reloadData方法估計是最頻繁調用的API了,而且是用來重新加載數據的重要方法,因此reloadData方法就是我們今天的主角了。
監(jiān)聽reloadData
? 需要監(jiān)聽reloadData方法的調用,有多種方式可選,一是直接繼承UITableView,在子類中監(jiān)聽;還有一種,就是利用運行時的一些特性。顯示,如果項目不是重新開始,自然后一種是更好的選擇。新建UITableView分類,替換reloadData
/**
* 在這里將reloadData方法進行替換
*/
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// 替換三個方法
SEL originalSelector = @selector(reloadData);
SEL swizzledSelector = @selector(_hp_reloadData);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL needAddMethod =
class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if ( needAddMethod ) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- 選擇是否需要自動管理
由于并不是總是所有的UITableView都需要顯示錯誤頁面,所以添加一個控制,只有當設置成自動管理錯誤頁的時候才會觸發(fā)檢查是否需要顯示錯誤頁。
@interface UITableView (HPAdd)
/**
是否需要自動管理顯示錯誤頁【默認不需要】
*/
@property (nonatomic, assign, getter=isAutoControlErrorView) BOOL autoControlErrorView;
@end
///////////////////////////////////////////////////////////////////
@implementation UITableView (HPAdd)
- (void)_hp_reloadData {
if (self.isAutoControlErrorView) {
[self _checkIsEmpty];
}
[self _hp_reloadData];
}
@end
- 檢查是否需要顯示錯誤頁
?判斷當前是否有數據,沒有數據則需要顯示錯誤頁。
- (void)_checkIsEmpty {
BOOL isEmpty = YES;
id <UITableViewDataSource> dataSource = self.dataSource;
NSInteger sections = 1; // 默認顯示1組
if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
sections = [dataSource numberOfSectionsInTableView:self];
}
for (NSInteger i = 0; i < sections; i++) {
NSInteger rows = [dataSource tableView:self numberOfRowsInSection:i];
if (rows != 0) {
isEmpty = NO; // 若行數存在,則數據不為空
break;
}
}
dispatch_main_async_safe(^{
if (isEmpty) {
[self _showErrorView]; // 顯示錯誤頁
} else {
[self _removeErrorView]; // 移除錯誤頁
}
});
}
總結
對于前兩種方法,優(yōu)點是我們控制起來更加的靈活,可以進行更多個性化的定制;缺點是代碼比較重復,而且耦合比較的嚴重。
?對于自動管理,優(yōu)點是我們不需要關心內部的實現,可以專心寫自己的邏輯。其次耦合比較低,分類中更改的代碼不會影響到其他地方。缺點是不便于個性化,當需要大量的個性化設置時控制變量就會讓代碼很不友好了。
?另外需要注意的就是,調整UI需要在主線程中進行。Demo請點擊