iOS實現錯誤提示頁自動管理

疑問?

在開發(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請點擊

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容