EmptyPage(空白页组件)原理与使用

app 显示列表内容时, 在某一时刻可能数据为空(等待网络请求/网络请求失败)等, 添加一个空白指示页将有效缓解用户可能造成的焦虑或混乱. 并可以帮助用户处理问题.

市面上已经有部分成熟的空白页框架,最典型的就是使用DZNEmptyDataSet.

但是其使用DZNEmptyDataSetDelegate,DZNEmptyDataSetSource来定制空白页元素,使用时较为繁琐.

笔者借鉴其原理的基础上,制作了对标框架(单向对标)EmptyPage来简化日常项目开发.

前言

EmptyPage 历时1年, 在我司项目中稳定使用迭代6个版本,算是比较稳定.

支持UICollectionView & UITableView.

ps: 目前阶段只提供 swift 版本.

image
image

实现原理

该核心部分 作为一个单独的子库 实现, 可使用 以下方式单独引用.

pod 'EmptyPage/Core'

具体代码可查阅 Github Link, 超级简单.

  1. UIScrollView添加emptyView对象作为空白页实例:
    public extension UIScrollView {
      public var emptyView: UIView?
    }
    
  2. Method Swizzling方式替换掉UITableView \ UICollectionView 中部分相关函数.以下拿UITableView 举例:
    // DZNEmptyDataSet 对 autolayout 项目不太友好. (也可能本人没深度使用...)
    // EmptyPage 
    // UITableView frame 变化相关函数
    open func layoutSubviews()
    open func layoutIfNeeded()
    // 数据源增减相关函数
    open func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
    open func deleteRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
    open func insertSections(_ sections: IndexSet, with animation: UITableView.RowAnimation)
    open func deleteSections(_ sections: IndexSet, with animation: UITableView.RowAnimation)
    open func reloadData()
    
  3. 在数据/frame变化时判断空白页显示与隐藏.
    func setEmptyView(event: () -> ()) {
        oldEmptyView?.removeFromSuperview()
        event()
        guard bounds.width != 0, bounds.height != 0 else { return }
        var isHasRows = false
        let sectionCount = dataSource?.numberOfSections?(in: self) ?? numberOfSections
        for index in 0..<sectionCount {
          if numberOfRows(inSection: index) > 0 {
            isHasRows = true
            break
          }
        }
        isScrollEnabled = isHasRows
        if isHasRows {
          emptyView?.removeFromSuperview()
          return
        }
        guard let view = emptyView else{ return }
        view.frame = bounds
        addSubview(view)
        sendSubview(toBack: view)
      }
    
  4. 使用

    UITableView().emptyView = CustomView()
    UICollectionView().emptyView = CustomView()
    

    UITableView().emptyView 第一次被赋值时才会进行 Method Swizzling 相关函数.

模板视图

DZNEmptyDataSet 的成功离不开其可高度定制化的模板视图.但其繁琐的 delegate apis 远不如自定义视图来的方便, 其对自定义视图的支持也并不友善.

EmptyPage 优先支持 自定义视图,并附赠 3 套可以凑合看的模板视图(支持超级高自定义调节,但毕竟UI我们说了不算...)

采用 以下方式 则包含该部分内容:

pod 'EmptyPage'
  1. 自定义视图
    • 仅支持autolayout布局模式

      不使用 autolayout 模式:

      1. pod 'EmptyPage/Core'

      2. UITableView().emptyView = CustomView()

    • 自定义视图需要autolayout实现自适应高

      可以参考 内置的几套模板视图的约束实现.

    • 添加 EmptyPageContentViewProtocol 协议

      该协议默认实现了将自定义视图居中约束至一个backgroundView上.

      通用性考虑: backgroundView.frame 与 tableView.frame 相同

      示例:

      class CustomView: EmptyPageContentViewProtocol{
          ...
      }
      
      let customView = CustomView()
      UITableView().emptyView = customView.mix()
      

      不添加该协议,可采用以下方式:

      UITableView().emptyView = EmptyPageView.mix(view: customView)

    • 视图关系

      视图关系
  2. 内置模板视图

    **特性: **

    1. 支持链式调用.
    2. 元素支持高度自定义.
    3. 同样依照自定义视图的标准实现.

    ps: 完全等同于提前写好的自定义模板视图.

    • 目前可以选择3套基本的模板视图.
      • 文字模板(EmptyPageView.ContentView.onlyText)

      • 图片模板(EmptyPageView.ContentView.onlyImage)

      • 混合模板(EmptyPageView.ContentView.standard)

    文字模板
    图片模板
    混合模板
  • 使用

    • 示例:

      UITableView().emptyView = EmptyPageView.ContentView.standard
       .change(hspace: .button, value: 80)
       .change(height: .button, value: 60)
       .change(hspace: .image, value: 15)
       .config(button: { (item) in
           item.backgroundColor = UIColor.blue
           item.contentEdgeInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20)
       })
       .set(image: UIImage(named: "empty-1002")!)
       .set(title: "Connection failure", color: UIColor.black, font: UIFont.boldSystemFont(ofSize: 24))
       .set(text: "Something has gone wrong with the internet connection. Let's give it another shot.", color: UIColor.black, font: UIFont.systemFont(ofSize: 15))
       .set(buttonTitle: "TRY AGAIN")
       .set(tap: {
       // 点击事件
       })
       .mix()
      
  • Apis

    模板视图中总结起来只有三种配置函数:

    • 约束配置函数: func change(...) -> Self

      约束函数具体可配置项采用枚举的形式限定.(以免改变/冲突自适应高度相关约束)

      enum HSpaceType { } // 修改视图水平方向上的间距

      enum VSpaceType { } // 修改视图垂直方向上的间距

      enum HeightType { } // 修改视图具体高度

      例如:

      standardView.change(hspace: .button, value: 80)
                 .change(height: .button, value: 60)
      
    • 控件配置函数: func set(...) -> Self

      提供了简单的文本/字体/图片/颜色配置.例如:

      standardView.set(title: "Connection failure", color: UIColor.black, font: UIFont.boldSystemFont(ofSize: 24))
      
    • 控件自定义配置函数: func config(element: { (element) in ... }) -> Self

      返回一个完整的控件,可供深度配置. 例如:

      standardView.config(button: { (item) in
         item.backgroundColor = UIColor.blue
         item.contentEdgeInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20)
         })
      
    • 视图混合函数func mix():

      该函数由 EmptyPageContentViewProtocol 协议默认实现.

      作用: 将视图约束至 backgroundView 上

      ps: 别忘了...

结尾

项目开源链接: Github/EmptyPage

个人博客链接: 四方田

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,960评论 3 119
  • 我的妈妈,今年56岁,头发半白,是一个读到小学二年级就辍学回家干农活的女人。她个头不高,身材中等,文化水平不高,没...
    空中之鸟阅读 359评论 0 0
  • 对于我们的眼睛,不是缺少美,而是缺少发现——看Matizki的摄影作品,你会更深刻地体悟这句话。 Matizki是...
    荻子荻子阅读 380评论 0 2
  • ——魏君学习非暴力沟通心得 “我这样做对吗?” “我这衣服漂亮吗?” “他让我感到自己很有价值。” “他会怎么看我...
    魏君NVC阅读 378评论 2 2