一个Swift语言封装的EmptyView显示库,可作用于WKWebView、UITableView、UICollectionView
示例
-
WKWebView
-
UITableView
- UICollectionView
引言:
项目开发过程中当网络断开或者数据获取失败导致的界面显示为空的情况下,我们常会用到图片加文字加刷新按钮、文字加刷新按钮或者纯文本提醒的空界面显示,所以对该功能实现的封装封装就显得很有必要。
该技术封装模块使用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() 方法进行显隐操作