美文网首页聊天功能设计
聊天功能(二)--创建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