美文网首页聊天功能设计
聊天功能(二)--创建ChatViewCell

聊天功能(二)--创建ChatViewCell

作者: Mage | 来源:发表于2018-06-06 17:36 被阅读40次

    一、创建ChatViewCell

    首先需要创建根视图ChatViewCell,然后所有的MessageCell都继承ChatViewCell,这样方便以后拓展通用功能.

    public class ChatViewCell: UITableViewCell {
        public var message: Message?
    }
    

    二、创建ChatSystemCell

    ChatSystemCell是系统消息,一个居中的UILabel,外围添加背景框,布局如下图:


    ChatSystemCell.png
    //
    //  ChatSystemCell.swift
    //
    import UIKit
    import Cartography
    
    public class ChatSystemCell: ChatViewCell {
        public let systemLabel = UILabel.init()
        
        override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            
            let bgView = UIView.init()
            bgView.layer.cornerRadius = 5
            bgView.backgroundColor = UIColor.colorFromRGB(0xebebf1)
            bgView.addSubview(systemLabel)
            self.contentView.addSubview(bgView)
            
            systemLabel.numberOfLines = 0
            systemLabel.font = UIFont.systemFont(ofSize: 16)
            systemLabel.textAlignment = .center
            
            constrain(systemLabel, bgView) { (systemLabel, bgView) in
                systemLabel.height >= 10
                systemLabel.edges == bgView.edges.inseted(top: 5, leading: 10, bottom: 5, trailing: 10)
                
                bgView.centerX == bgView.superview!.centerX
                bgView.top == bgView.superview!.top + 10
                bgView.bottom == bgView.superview!.bottom - 10
                bgView.left >= bgView.superview!.left + 50
                bgView.right <= bgView.superview!.right - 50
            }
            
        }
        required public init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override public var message: Message? {
            didSet {
                if let message = message {
                    systemLabel.text = message.comment
                }
            }
        }
    }
    

    三、创建其他消息的父类ChatMessageCell
    由于普通消息的头像和气泡会因为角色出现在左右两侧的情况,这就应该统一在ChatMessageCell中根据角色的不同统一处理,之后在子类里就无需关心头像和气泡了.


    ChatMessageCell.png
    ChatMessageCell@2x_spec.png

    1、在init里创建基本布局,将那些基本不会变的布局先设置好
    2、在设置Message时,根据角色的不同,改变UI的布局.同时设置view的内容

    public class ChatMessageCell: ChatViewCell {
    
        private var messageView: UIView!
        private var backgroundImageView: UIImageView!
        
        public let headerView: UIImageView = UIImageView.init()
        public let timeLabel: UILabel = UILabel.init()
        public let idLabel: UILabel = UILabel.init()
        
        public init(style: UITableViewCellStyle, reuseIdentifier: String?, messageView: UIView, backgroundImageView: UIImageView = UIImageView.init()) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            
            setupLayout(messageView: messageView, backgroundImageView: backgroundImageView)
        }
        
        private func setupLayout(messageView: UIView, backgroundImageView: UIImageView) {
            // 基本布局
        }
    
        public override var message: Message? {
            didSet {
                if let message = message {
                    let from = message.from
                    assert(from != .system, "类型不正确")
                    // 更新布局
                    if from == .me {
                        // 改变UI布局,左边内容右边头像
                    }else{
                        // 改变UI布局,右边内容左边头像
                    }
                    // 设置数据
                }
            }
        }
    }
    

    这里有一点需求,就是图片消息需要做成像微信那样没有气泡的样子(如下图),因为没有背景图,所以我们需要将ChatMessageCell中的backgroundImageView设置成Optional的,由子类控制是否需要气泡背景.所以代码需要修改下


    ChatImageCell.png
    public class ChatMessageCell: ChatViewCell {
    
        private var messageView: UIView!
        private var backgroundImageView: UIImageView?
        
        public let headerView: UIImageView = UIImageView.init()
        public let timeLabel: UILabel = UILabel.init()
        public let idLabel: UILabel = UILabel.init()
        
        private var gestureRecognizerTuple: (UITapGestureRecognizer?, UILongPressGestureRecognizer?)
        public var tapGestureBlock: ((UIGestureRecognizer) -> Void)? {
            didSet {
                let view = actionView
                if tapGestureBlock != nil {
                    gestureRecognizerTuple.0 = UITapGestureRecognizer.init(target: self, action: #selector(tapAction(_:)))
                    view.addGestureRecognizer(gestureRecognizerTuple.0!)
                }else{
                    view.removeGestureRecognizer(gestureRecognizerTuple.0!)
                    gestureRecognizerTuple.0 = nil
                }
            }
        }
    
        public var longPressGestureBlock: ((UILongPressGestureRecognizer) -> Void)? {
            didSet {
                let view = actionView
                if longPressGestureBlock != nil {
                    gestureRecognizerTuple.1 = UILongPressGestureRecognizer.init(target: self, action: #selector(longPressAction(_:)))
                    view.addGestureRecognizer(gestureRecognizerTuple.1!)
                }else{
                    view.removeGestureRecognizer(gestureRecognizerTuple.1!)
                    gestureRecognizerTuple.1 = nil
                }
            }
        }
        
        private var actionView: UIView {
            var view: UIView!
            if let imageView = backgroundImageView {
                messageView.isUserInteractionEnabled = false
                backgroundImageView?.isUserInteractionEnabled = true
                view = imageView
            }else{
                messageView.isUserInteractionEnabled = true
                view = messageView
            }
            return view
        }
        
        override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
            fatalError("should use init(style:reuseIdentifier:messageView:backgroundImageView:) initialize")
        }
        public init(style: UITableViewCellStyle, reuseIdentifier: String?, messageView: UIView, backgroundImageView: UIImageView? = UIImageView.init()) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            
            setupLayout(messageView: messageView, backgroundImageView: backgroundImageView)
        }
        
        required public init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        @objc func longPressAction(_ longPress: UILongPressGestureRecognizer){
            longPressGestureBlock!(longPress)
        }
        
        @objc func tapAction(_ tap: UITapGestureRecognizer) {
            tapGestureBlock!(tap)
        }
        
        private func setupLayout(messageView: UIView, backgroundImageView: UIImageView?) {
    
            self.messageView = messageView
            self.backgroundImageView = backgroundImageView
    
            timeLabel.font = UIFont.systemFont(ofSize: 14)
            idLabel.textAlignment = .center
            idLabel.adjustsFontSizeToFitWidth = true
    
            self.contentView.addSubview(headerView)
            self.contentView.addSubview(messageView)
            self.contentView.addSubview(timeLabel)
            self.contentView.addSubview(idLabel)
    
            if let backgroundImageView = backgroundImageView {
                self.backgroundImageView = backgroundImageView
                self.contentView.insertSubview(backgroundImageView, belowSubview: messageView)
    
                constrain(timeLabel, headerView, messageView, backgroundImageView, idLabel) { (timeLabel, headerView, messageView, backgroundImageView, idLabel) in
                    let superview = timeLabel.superview!
    
                    timeLabel.top == superview.top
                    timeLabel.centerX == superview.centerX
    
                    headerView.height == 44
                    headerView.width == 44
                    idLabel.edges == headerView.edges
    
                    headerView.top == backgroundImageView.top
                    // headerView left and right depend message.from
    
                    backgroundImageView.top == timeLabel.bottom + 10
                    backgroundImageView.bottom == superview.bottom - 10
                    // backgroundImageView leading and trailing depend message.from
    
                    messageView.height >= 24.5
                    messageView.width >= 5
                }
            } else {
                constrain(timeLabel, headerView, messageView, idLabel) { (timeLabel, headerView, messageView, idLabel) in
                    let superview = timeLabel.superview!
    
                    timeLabel.top == superview.top
                    timeLabel.centerX == superview.centerX
    
                    headerView.height == 44
                    headerView.width == 44
                    idLabel.edges == headerView.edges
    
                    headerView.top == messageView.top
                    // headerView left and right depend message.from
    
                    messageView.top == timeLabel.bottom + 10
                    messageView.bottom == superview.bottom - 10
                    // backgroundImageView leading and trailing depend message.from
    
                    messageView.height >= 44
                    messageView.width >= 20
                }
            }
        }
        private let group = ConstraintGroup()
        public override var message: Message? {
            didSet {
                if let message = message {
    
                    let from = message.from
                    assert(from != .system, "类型不正确")
                    // 更新布局
                    if let backgroundImageView = backgroundImageView {
                        constrain(headerView, messageView, backgroundImageView, replace: group) { (headerView, messageView, backgroundImageView) in
                            let superview = headerView.superview!
    
                            if from == .me {
                                headerView.right == superview.right - 20
                                backgroundImageView.edges == messageView.edges.inseted(top: -10, leading: -10, bottom: -10, trailing: -17)
                                backgroundImageView.left >= superview.left + 80
                                backgroundImageView.right == headerView.left - 3
                            }else{
                                headerView.left == superview.left + 20
                                backgroundImageView.edges == messageView.edges.inseted(top: -10, leading: -17, bottom: -10, trailing: -10)
                                backgroundImageView.right <= superview.right - 80
                                backgroundImageView.left == headerView.right + 3
                            }
                        }
                    } else {
                        constrain(headerView, messageView, replace: group) { (headerView, messageView) in
                            let superview = headerView.superview!
    
                            if from == .me {
                                headerView.right == superview.right - 20
                                messageView.left >= superview.left + 80
                                messageView.right == headerView.left - 10
                            }else{
                                headerView.left == superview.left + 20
                                messageView.right <= superview.right - 80
                                messageView.left == headerView.right + 10
                            }
                        }
                    }
    
                    // 设置数据
                    idLabel.text = "\(message.id ?? -1)"
                    timeLabel.text = message.isShowTime ? "\(Date.init(timeIntervalSinceReferenceDate:message.timestamp).chatString)" : nil
                    let headerName = message.from == .me ? "user_agent" : "user_ customer"
                    var image: UIImage? = imageCache().object(forKey: headerName as AnyObject) as? UIImage
                    if let image = image {
                        headerView.image = image
                    }else{
                        image = UIImage.init(named: headerName)?.ellipseImage()
                        headerView.image = image
                        DispatchQueue.global().async {
                            imageCache().setObject(image!, forKey: headerName as AnyObject)
                        }
                    }
    
                    if let backgroundImageView = backgroundImageView {
                        let bgName = message.from == .me ? "chat_messagebg_me" : "chat_messagebg_other"
                        backgroundImageView.image = UIImage.init(named: bgName)
                        backgroundImageView.highlightedImage = UIImage.init(named: "\(bgName)_pre")
                    }
                }
            }
        }
    }
    

    上述代码中同时加入的单击和双击的事件处理,当子类需要这些事件时,只需要添加tapGestureBlock或longPressGestureBlock即可支持点击或双击功能.

    上一篇:聊天功能(一)--Message模型
    下一篇:聊天功能(三)--创建TextCell、ImageCell等

    Demo地址:MAChatTableViewDemo

    相关文章

      网友评论

        本文标题:聊天功能(二)--创建ChatViewCell

        本文链接:https://www.haomeiwen.com/subject/qiibsftx.html