Swift 空資料介面顯示模組封裝實現

weixin_34146805發表於2018-07-09

一個Swift語言封裝的EmptyView顯示庫,可作用於WKWebView、UITableView、UICollectionView

示例

  • WKWebView


    6152078-8db8726a8c17b7d5.gif
    webViewEmptyGIF.gif
  • UITableView


    6152078-afa1633fb19457b0.gif
    tableViewEmptyGIF.gif
  • UICollectionView
6152078-c322d54aafdf02e5.gif
collectionViewEmptyGIF.gif

引言:

專案開發過程中當網路斷開或者資料獲取失敗導致的介面顯示為空的情況下,我們常會用到圖片加文字加重新整理按鈕、文字加重新整理按鈕或者純文字提醒的空介面顯示,所以對該功能實現的封裝封裝就顯得很有必要。

該技術封裝模組使用Swift語言,參考OC封裝模組的內部實現邏輯,利用runtime的系統方法交換機制,實現在WKWebView網頁載入介面、UITableView、UICollectionView列表檢視等介面資料獲取失敗情況下的提醒顯示和重新整理操作功能。

一:內部實現原理

1、通過runtime key值關聯HDEmptyView顯示物件

建立UIScrollView的extension物件UIScrollView+Empty類,通過runtime key值關聯HDEmptyView顯示介面物件ly_emptyView ,該物件可根據呼叫介面的引數設定來控制空介面顯示的內容、佈局樣式、顏色等。

    struct RuntimeKey {
        static let kEmptyViewKey = UnsafeRawPointer.init(bitPattern: "kEmptyViewKey".hashValue)
    }
    
    public var ly_emptyView: HDEmptyView? {
        set {
            objc_setAssociatedObject(self, RuntimeKey.kEmptyViewKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            for view in self.subviews {
                if view.isKind(of: HDEmptyView.classForCoder()) {
                    view.removeFromSuperview()
                }
            }
         self.addSubview(ly_emptyView!)
         self.ly_emptyView?.isHidden = true
        }
        get {
            return objc_getAssociatedObject(self, RuntimeKey.kEmptyViewKey!) as? HDEmptyView
        }
    }
    

2、WKWebView 呼叫顯隱方法

如果是WKWebView的空資料介面顯示,根據介面載入成功或失敗的情況,呼叫顯示/隱藏空介面方法

    public func ly_showEmptyView() {
        self.ly_emptyView?.superview?.layoutSubviews()
        self.ly_emptyView?.isHidden = false
        //始終保持顯示在最上層
        if self.ly_emptyView != nil {
            self.bringSubview(toFront: self.ly_emptyView!)
        }
    }
    
    public func ly_hideEmptyView() {
        self.ly_emptyView?.isHidden = true
    }  

3、列表檢視顯隱控制

如果是UITableView、UICollectionView則根據DataSource判斷是否自動顯示emptyView

首先獲取當前列表檢視上cell的個數

   //MARK: - Private Method
    fileprivate func totalDataCount() -> NSInteger {
        var totalCount: NSInteger = 0
        if self.isKind(of: UITableView.classForCoder()) {
            let tableView = self as? UITableView
            if (tableView?.numberOfSections)! >= 1 {
                for section in 0...(tableView?.numberOfSections)!-1 {
                    totalCount += (tableView?.numberOfRows(inSection: section))!
                }
            }
        }
        else if self.isKind(of: UICollectionView.classForCoder()) {
            let collectionView = self as? UICollectionView
            if (collectionView?.numberOfSections)! >= 1 {
                for section in 0...(collectionView?.numberOfSections)!-1 {
                    totalCount += (collectionView?.numberOfItems(inSection: section))!
                }
            }
        }
        return totalCount
    }

然後根據cell的個數判斷是否顯示 emptyView

    fileprivate func getDataAndSet() {
        if self.totalDataCount() == 0 {
            show()
        } else {
            hide()
        }
    }
    
    fileprivate func show() {
        if self.ly_emptyView?.autoShowEmptyView == false {
            self.ly_emptyView?.isHidden = true
            return
        }
        ly_showEmptyView()
    }

    fileprivate func hide() {
        if self.ly_emptyView?.autoShowEmptyView == false {
            self.ly_emptyView?.isHidden = true
            return
        }
        ly_hideEmptyView()
    }

4、列表檢視的方法交換與介面重新整理顯示

    private static let swizzleMethod: Void = {
        //insertSections
        let originalSelector = #selector(insertSections(_:with:))
        let swizzledSelector = #selector(ly_insertSections(_:with:))
        HDRunTime.exchangeMethod(selector: originalSelector, replace: swizzledSelector, class: UITableView.self)
        
        //deleteSections
        let originalSelector1 = #selector(deleteSections(_:with:))
        let swizzledSelector1 = #selector(ly_deleteSections(_:with:))
        HDRunTime.exchangeMethod(selector: originalSelector1, replace: swizzledSelector1, class: UITableView.self)
        
        //insertRows
        let originalSelector2 = #selector(insertRows(at:with:))
        let swizzledSelector2 = #selector(ly_insertRowsAtIndexPaths(at:with:))
        HDRunTime.exchangeMethod(selector: originalSelector2, replace: swizzledSelector2, class: UITableView.self)
        
        //deleteRows
        let originalSelector3 = #selector(deleteRows(at:with:))
        let swizzledSelector3 = #selector(ly_deleteRowsAtIndexPaths(at:with:))
        HDRunTime.exchangeMethod(selector: originalSelector3, replace: swizzledSelector3, class: UITableView.self)
        
        //reload
        let originalSelector4 = #selector(reloadData)
        let swizzledSelector4 = #selector(ly_reloadData)
        HDRunTime.exchangeMethod(selector: originalSelector4, replace: swizzledSelector4, class: UITableView.self)
        
    }()
    

    //section
    @objc  func ly_insertSections(_ sections: NSIndexSet, with animation: UITableViewRowAnimation) {
        ly_insertSections(sections, with: animation)
        getDataAndSet()
    }
    
    @objc  func ly_deleteSections(_ sections: NSIndexSet, with animation: UITableViewRowAnimation) {
        ly_deleteSections(sections, with: animation)
        getDataAndSet()
    }
    
    //row
    @objc  func ly_insertRowsAtIndexPaths(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation){
        ly_insertRowsAtIndexPaths(at: indexPaths, with: animation)
        getDataAndSet()
    }
    
    @objc func ly_deleteRowsAtIndexPaths(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation){
        ly_deleteRowsAtIndexPaths(at: indexPaths, with: animation)
        getDataAndSet()
    }
    
    //reloadData
    @objc func ly_reloadData() {
        self.ly_reloadData()
        self.getDataAndSet()
    }

二:使用方法

1、建立 HDEmptyView 介面顯示物件

      //建立方式一:Block回撥
     let emptyV:HDEmptyView = HDEmptyView.emptyActionViewWithImageStr(imageStr: "net_error_tip", titleStr: "暫無資料,點選重新載入", detailStr: "", btnTitleStr: "點選重新整理") {
            print("點選重新整理")
            weakSelf?.reloadDataWithCount(count: 4)
        }
        
        //建立方式二:target/action
    let emptyV:HDEmptyView = HDEmptyView.emptyActionViewWithImageStr(imageStr: "net_error_tip", titleStr: "暫無資料,點選重新載入", detailStr: "", btnTitleStr: "點選重新整理", target: self, action: #selector(reloadBtnAction)) as! HDEmptyView
 

2、設定顯示引數屬性

        emptyV.titleLabTextColor = UIColor.red
        emptyV.actionBtnFont = UIFont.systemFont(ofSize: 19)
        emptyV.contentViewOffset = -50
        emptyV.actionBtnBackGroundColor = .white
        emptyV.actionBtnBorderWidth = 0.7
        emptyV.actionBtnBorderColor = UIColor.gray
        emptyV.actionBtnCornerRadius = 10

3、賦值給當前顯示物件的ly_emptyView

webView.scrollView.ly_emptyView = emptyV

tableView.ly_emptyView = emptyV

collectionView.ly_emptyView = emptyV

//設定點選空白區域是否有重新整理操作
        self.webView.scrollView.ly_emptyView?.tapContentViewBlock = {
            //weakSelf!.loadingURL(urltring: "http://news.baidu.com/")
        }


4、自定義空資料介面顯示

    //自定義空資料介面顯示
    func setupMyEmptyView() {
        let emptyView: MyEmptyView = Bundle.main.loadNibNamed("MyEmptyView", owner: self, options: nil)?.last as! MyEmptyView
        emptyView.reloadBtn.addTarget(self, action: #selector(reloadBtnAction(_:)), for: UIControlEvents.touchUpInside)
        emptyView.frame = view.bounds
        //空資料介面顯示
        let emptyV:HDEmptyView = HDEmptyView.emptyViewWithCustomView(customView: emptyView) as! HDEmptyView
        tableView.ly_emptyView = emptyV
        tableView.ly_emptyView?.tapContentViewBlock = {
            print("點選介面空白區域")
        }
        tableView.ly_showEmptyView()
    }
    

注意事項:

是否自動顯隱EmptyView的引數 autoShowEmptyView 預設設定是true,列表檢視會根據介面cell的count數量自動顯隱空介面。
當設定成false時只能手動呼叫 ly_showEmptyView() 和 ly_hideEmptyView() 方法進行顯隱操作

下載地址

相關文章