聊天功能(五)--ChatTableView的刷新能力Reloadable

一、Reloadable定义

在处理聊天功能时,有可能会出现以下几种场景:
1、添加消息,需要使用UITableView.insertRows(at:with:);
2、撤回消息,需要显示内容“该消息已被对方撤回”,需要使用UITableView.reloadRows(at:with:);
3、删除消息,需要使用UITableView.deleteRows(at:with:)。

创建Reloadable协议,使UITableView同时支持添加消息、刷新消息、删除消息的能力。首先先将协议定义好。支持协议的类需要提供models和刷新使用的tableView,并且model必须支持Comparable。之后直接调用refresh()方法即可更新数据。

// MARK: - UITableView扩展
public protocol Reloadable {

    associatedtype Model: Comparable, Hashable
    
    var models: [Model] { get set }
    var reloadTableView: UITableView { get }
    
    mutating func refresh(_ models: [(Model, RefreshMode)])
}
extension Reloadable where Model: Comparable {
    
    public mutating func refresh(_ models: [(Model, RefreshMode)]){
    }
}

二、RefreshMode定义

RefreshMode用于定义消息是插入还是删除(更新消息不能确定这个消息是否已经存在,所以将其并入insert里,当存在该消息时,则就是更新消息)。同时还需要指定该消息插入或删除后,是否将tableView滚动到底部。

public enum ScrollType {
    case none   // 不滚动
    case bottom // 滚动到底部
    case hold   // 滚动到原来的位置,界面上不动
}
public enum RefreshMode {
    case insert(ScrollType) // 插入数据
    case delete(ScrollType) // 删除数据
    
    public var type: ScrollType {
        switch self {
        case .insert(let type):
            return type
        case .delete(let type):
            return type
        }
    }
    
    public var isDelete: Bool {
        switch self {
        case .delete(_):
            return true
        default:
            return false
        }
    }
    
    public var isInsert: Bool {
        switch self {
        case .insert(_):
            return true
        default:
            return false
        }
    }
}

3、refresh方法

refresh方法接收[(Model, RefreshMode)]数组,里面包含models和model的刷新策略。

1、需要将models根据刷新策略分类成deleteModels、insertModels和reloadModels。
2、self.models删除掉deleteModels,然后添加insertModels。
3、计算对应的indexPath数组。deleteIndexPaths和reloadIndexPaths需要根据oldModels对应的位置创建indexPath,insertIndexPaths需要根据更新后的self.models对应的位置创建indexPath。
4、判断滚动策略:
4.1、如果scrollType == .bottom,则直接使用scrollToRow滚动到tableView的底部
4.2、如果scrollType == .hold,由于如果models中存在删除或插入的数据,tableView上的可见的cell会向上或向下移动,体验不好,所以需要计算tableView可见的最后一个cell的位置的偏移量,当刷新完row时重新设置偏移量,达到界面保持不动的效果。
5、更新Rows,刷新操作需要放在beginUpdates()和endUpdates()

extension Reloadable where Model: Comparable {
    
    public mutating func refresh(_ models: [(Model, RefreshMode)]){
        
        if models.count == 0 { return }
        let oldModels = self.models
        
        var addModels: Set<Model> = Set()
        var deleteModels: Set<Model> = Set()
        
        var scrollType: ScrollType = .none
        models.forEach { (model, type) in
            scrollType = type.type
            if type.isInsert {
                deleteModels.remove(model)
                addModels.insert(model)
            }else{
                addModels.remove(model)
                deleteModels.insert(model)
            }
        }
        // 处理数据
        self.models.remove(contentsOf: deleteModels)
        
        let orders = self.models.insertOrder(contentsOf: addModels)
        
        let deleteIndexPaths: [IndexPath] = deleteModels.compactMap{ (model) -> IndexPath? in
            if let index = oldModels.index(of: model) {
                return IndexPath.init(row: index, section: 0)
            }else{ return nil }
        }
        let reloadIndexPaths: [IndexPath] = orders.1.compactMap { (model) -> IndexPath? in
            if let index = oldModels.index(of: model) {
                return IndexPath.init(row: index, section: 0)
            }else{ return nil }
        }
        let insertIndexPaths: [IndexPath] = orders.0.compactMap { (model) -> IndexPath? in
            if let index = self.models.index(of: model) {
                return IndexPath.init(row: index, section: 0)
            }else{ return nil }
        }
        
        var bottomIndexPath: IndexPath? = nil
        // bottom的偏移量
        var offsetInset: CGFloat? = nil
        if let cell = self.reloadTableView.visibleCells.last, let indexPath = self.reloadTableView.indexPath(for: cell), indexPath.row < oldModels.count {
            let model = oldModels[indexPath.row]
            if let index = self.models.index(of: model) {
                bottomIndexPath = IndexPath.init(row: index, section: 0)
                offsetInset = cell.frame.maxY - self.reloadTableView.frame.height - self.reloadTableView.contentOffset.y
            }
        }
        UIView.noAnimation {
            self.reloadTableView.beginUpdates()
            if deleteIndexPaths.count > 0{
                self.reloadTableView.deleteRows(at: deleteIndexPaths, with: .none)
            }
            if insertIndexPaths.count > 0{
                self.reloadTableView.insertRows(at: insertIndexPaths, with: .none)
            }
            if reloadIndexPaths.count > 0{
                self.reloadTableView.reloadRows(at: reloadIndexPaths, with: .none)
            }
            self.reloadTableView.endUpdates()
            
            if scrollType == .bottom {
                self.reloadTableView.scrollToRow(at: IndexPath.init(row: self.models.count-1, section: 0), at: .bottom, animated: true)
            }else if scrollType == .hold && bottomIndexPath != nil && offsetInset != nil{
                self.reloadTableView.scrollToRow(at: bottomIndexPath!, at: .none, animated: false)
                var offset = self.reloadTableView.contentOffset
                offset.y -= offsetInset!
                self.reloadTableView.contentOffset = offset
            }
        }
    }
}

上一篇:聊天功能(四)--创建ChatViewController

Demo地址:MAChatTableViewDemo

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

推荐阅读更多精彩内容

  • 概述在iOS开发中UITableView可以说是使用最广泛的控件,我们平时使用的软件中到处都可以看到它的影子,类似...
    liudhkk阅读 8,978评论 3 38
  • 一、简介 <<UITableView(或简单地说,表视图)的一个实例是用于显示和编辑分层列出的信息的一种手段 <<...
    无邪8阅读 10,574评论 3 3
  • { 24、Sqlite数据库 1、存储大数据量,增删改查,常见管理系统:Oracle、MSSQLServer、DB...
    CYC666阅读 925评论 0 1
  • 【最近开始跑步了他瘦的好明显】 是的瘦的好明显每个朋友见到他都说哇你怎么瘦了这么多! 他也挺得意! 上个月开始跑步...
    一去三十年阅读 284评论 0 0
  • 这是第一次在白天用软件写些什么,很不一样的感觉,大脑会有些空白。白天的我早已习惯把所有情绪掩于内心,没什么,只是不...
    树风水阅读 85评论 0 0