{"id":596,"date":"2023-02-25T20:02:16","date_gmt":"2023-02-25T12:02:16","guid":{"rendered":"https:\/\/www.appblog.cn\/?p=596"},"modified":"2023-02-26T08:18:11","modified_gmt":"2023-02-26T00:18:11","slug":"swift-ui-customize-cells-to-realize-wechat-chat-view","status":"publish","type":"post","link":"https:\/\/www.appblog.cn\/index.php\/2023\/02\/25\/swift-ui-customize-cells-to-realize-wechat-chat-view\/","title":{"rendered":"Swift UI &#8211; \u81ea\u5b9a\u4e49\u5355\u5143\u683c\u5b9e\u73b0\u5fae\u4fe1\u804a\u5929\u754c\u9762"},"content":{"rendered":"<blockquote>\n<p>\u6ce8\uff1a\u4ee3\u7801\u5df2\u5347\u7ea7\u81f3Swift4<\/p>\n<\/blockquote>\n<h2>\u8bbe\u8ba1\u9700\u6c42<\/h2>\n<p>\u5fae\u4fe1\u804a\u5929\u754c\u9762\u7684\u6d88\u606f\u5c55\u793a\u5217\u8868\uff0c\u5b9e\u73b0\u7684\u529f\u80fd\u6709\uff1a<\/p>\n<p><!-- more --><\/p>\n<p>\uff081\uff09\u6d88\u606f\u53ef\u4ee5\u662f\u6587\u672c\u6d88\u606f\u4e5f\u53ef\u4ee5\u662f\u56fe\u7247\u6d88\u606f<br \/>\n\uff082\uff09\u6d88\u606f\u80cc\u666f\u4e3a\u6c14\u6ce1\u72b6\u56fe\u7247\uff0c\u540c\u65f6\u6d88\u606f\u6c14\u6ce1\u53ef\u6839\u636e\u5185\u5bb9\u81ea\u9002\u5e94\u5927\u5c0f<br \/>\n\uff083\uff09\u6bcf\u6761\u6d88\u606f\u65c1\u8fb9\u6709\u5934\u50cf\uff0c\u5728\u5de6\u8fb9\u8868\u793a\u53d1\u9001\u65b9\uff0c\u5728\u53f3\u8fb9\u8868\u793a\u63a5\u6536\u65b9<br \/>\n\uff084\uff09\u6d88\u606f\u6309\u5929\u5206\u7ec4\u5c55\u793a<br \/>\n\uff085\uff09\u589e\u52a0\u6d88\u606f\u53d1\u9001\u6846\uff0c\u53ef\u4ee5\u53d1\u9001\u548c\u5c55\u793a\u6d88\u606f<\/p>\n<h2>\u5b9e\u73b0\u601d\u8def<\/h2>\n<p>\uff081\uff09\u9700\u8981\u5b9a\u4e49\u4e00\u4e2a\u6570\u636e\u7ed3\u6784\u4fdd\u5b58\u6d88\u606f\u5185\u5bb9 MessageItem<br \/>\n\uff082\uff09\u7ee7\u627fUITableViewCell\u5b9e\u73b0\u81ea\u5b9a\u4e49\u5355\u5143\u683c\uff0c\u8fd9\u91cc\u9762\u653e\u5165\u5934\u50cf\u548c\u6d88\u606f\u4f53<br \/>\n\uff083\uff09\u7ee7\u627fUITableView\u5b9e\u73b0\u81ea\u5b9a\u4e49\u8868\u683c\uff0c\u901a\u8fc7\u8bfb\u53d6\u6570\u636e\u6e90\uff0c\u8fdb\u884c\u9875\u9762\u7684\u6e32\u67d3<br \/>\n\uff084\uff09\u6d88\u606f\u4f53\u6839\u636e\u5185\u5bb9\u7c7b\u578b\u4e0d\u540c\uff0c\u7528\u4e0d\u540c\u7684\u5c55\u793a\u65b9\u6cd5<br \/>\n\uff085\uff09\u6bcf\u4e2a\u5355\u5143\u683c\u7684\u9ad8\u5ea6\u9700\u8981\u6839\u636e\u5185\u5bb9\u8ba1\u7b97\u51fa\u6765<br \/>\n\uff086\uff09\u6570\u636e\u7531ViewController\u6765\u63d0\u4f9b\u521d\u59cb\u5316\u6570\u636e<\/p>\n<h2>\u4e3b\u8981\u4ee3\u7801<\/h2>\n<h3>\u4e3b\u9875\u9762 ViewController.swift<\/h3>\n<pre><code class=\"language-swift\">import UIKit\n\nclass ViewController: UIViewController, ChatDataSource, UITextFieldDelegate {\n\n    var Chats:NSMutableArray!\n    var tableView:TableView!\n    var me:UserInfo!\n    var you:UserInfo!\n    var txtMsg:UITextField!\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        setupChatTable()\n        setupSendPanel()\n    }\n\n    func setupSendPanel()\n    {\n        let screenWidth = UIScreen.main.bounds.width\n        let sendView = UIView(frame:CGRect(x: 0,y: self.view.frame.size.height - 56,width: screenWidth,height: 56))\n\n        sendView.backgroundColor = UIColor.lightGray\n        sendView.alpha = 0.9\n\n        txtMsg = UITextField(frame:CGRect(x: 7,y: 10,width: screenWidth - 95,height: 36))\n        txtMsg.backgroundColor = UIColor.white\n        txtMsg.textColor = UIColor.black\n        txtMsg.font = UIFont.boldSystemFont(ofSize: 12)\n        txtMsg.layer.cornerRadius = 10.0\n        txtMsg.returnKeyType = UIReturnKeyType.send\n\n        \/\/Set the delegate so you can respond to user input\n        txtMsg.delegate = self\n        sendView.addSubview(txtMsg)\n        self.view.addSubview(sendView)\n\n        let sendButton = UIButton(frame:CGRect(x: screenWidth - 80,y: 10,width: 72,height: 36))\n        sendButton.backgroundColor = UIColor(red: 0x37\/255, green: 0xba\/255, blue: 0x46\/255, alpha: 1)\n        sendButton.addTarget(self, action:#selector(ViewController.sendMessage) ,\n                            for:UIControlEvents.touchUpInside)\n        sendButton.layer.cornerRadius = 6.0\n        sendButton.setTitle(&quot;\u53d1\u9001&quot;, for:UIControlState())\n        sendView.addSubview(sendButton)\n    }\n\n    func textFieldShouldReturn(_ textField:UITextField) -&gt; Bool\n    {\n        sendMessage()\n        return true\n    }\n\n    func sendMessage()\n    {\n        \/\/composing=false\n        let sender = txtMsg\n        let thisChat = MessageItem(body:sender!.text! as NSString, user:me, date:Date(), mtype:ChatType.mine)\n        let thatChat = MessageItem(body:&quot;\u4f60\u8bf4\u7684\u662f\uff1a\\(sender!.text!)&quot; as NSString, user:you, date:Date(), mtype:ChatType.someone)\n\n        Chats.add(thisChat)\n        Chats.add(thatChat)\n        self.tableView.chatDataSource = self\n        self.tableView.reloadData()\n\n        \/\/self.showTableView()\n        sender?.resignFirstResponder()\n        sender?.text = &quot;&quot;\n    }\n\n    \/*\u521b\u5efa\u8868\u683c\u53ca\u6570\u636e*\/\n    func setupChatTable()\n    {\n        self.tableView = TableView(frame:CGRect(x: 0, y: 20, width: self.view.frame.size.width, height: self.view.frame.size.height - 76), style: .plain)\n\n        \/\/\u521b\u5efa\u4e00\u4e2a\u91cd\u7528\u7684\u5355\u5143\u683c\n        self.tableView!.register(TableViewCell.self, forCellReuseIdentifier: &quot;ChatCell&quot;)\n        me = UserInfo(name:&quot;Xiaoming&quot; ,logo:(&quot;xiaoming.png&quot;))\n        you = UserInfo(name:&quot;Xiaohua&quot;, logo:(&quot;xiaohua.png&quot;))\n\n        let zero = MessageItem(body:&quot;\u6700\u8fd1\u53bb\u54ea\u73a9\u4e86\uff1f&quot;, user:you, date:Date(timeIntervalSinceNow:-90096400), mtype:.someone)\n\n        let zero1 = MessageItem(body:&quot;\u53bb\u4e86\u8d9f\u676d\u5dde\uff0c\u660e\u5929\u53d1\u7167\u7247\u7ed9\u4f60\u54c8\uff1f&quot;, user:me, date:Date(timeIntervalSinceNow:-90086400), mtype:.mine)\n\n        let first = MessageItem(body:&quot;\u4f60\u770b\u8fd9\u98ce\u666f\u600e\u4e48\u6837\uff0c\u6211\u5468\u672b\u53bb\u676d\u5dde\u62cd\u7684\uff01&quot;, user:me, date:Date(timeIntervalSinceNow:-90000600), mtype:.mine)\n\n        let second = MessageItem(image:UIImage(named:&quot;example.png&quot;)!, user:me, date:Date(timeIntervalSinceNow:-90000290), mtype:.mine)\n\n        let third = MessageItem(body:&quot;\u592a\u8d5e\u4e86\uff0c\u6211\u4e5f\u60f3\u53bb\u90a3\u770b\u770b\u5462\uff01&quot;, user:you, date:Date(timeIntervalSinceNow:-90000060), mtype:.someone)\n\n        let fouth = MessageItem(body:&quot;\u55ef\uff0c\u4e0b\u6b21\u6211\u4eec\u4e00\u8d77\u53bb\u5427\uff01&quot;, user:me, date:Date(timeIntervalSinceNow:-90000020), mtype:.mine)\n\n        let fifth = MessageItem(body:&quot;\u4e09\u5e74\u4e86\uff0c\u6211\u7ec8\u7a76\u6ca1\u80fd\u770b\u5230\u8fd9\u4e2a\u98ce\u666f&quot;, user:you, date:Date(timeIntervalSinceNow:0), mtype:.someone)\n\n        Chats = NSMutableArray()\n        Chats.addObjects(from: [first,second, third, fouth, fifth, zero, zero1])\n\n        \/\/set the chatDataSource\n        self.tableView.chatDataSource = self\n\n        \/\/call the reloadData, this is actually calling your override method\n        self.tableView.reloadData()\n\n        self.view.addSubview(self.tableView)\n    }\n\n    \/*\u8fd4\u56de\u5bf9\u8bdd\u8bb0\u5f55\u4e2d\u7684\u5168\u90e8\u884c\u6570*\/\n    func rowsForChatTable(_ tableView:TableView) -&gt; Int\n    {\n        return self.Chats.count\n    }\n\n    \/*\u8fd4\u56de\u67d0\u4e00\u884c\u7684\u5185\u5bb9*\/\n    func chatTableView(_ tableView:TableView, dataForRow row:Int) -&gt; MessageItem\n    {\n        return Chats[row] as! MessageItem\n    }\n\n    override func didReceiveMemoryWarning() {\n        super.didReceiveMemoryWarning()\n    }\n}<\/code><\/pre>\n<h3>\u7528\u6237\u4fe1\u606f\u7c7b UserInfo.swift<\/h3>\n<pre><code class=\"language-swift\">import Foundation\n\n\/*\n * \u7528\u6237\u4fe1\u606f\u7c7b\n *\/\nclass UserInfo:NSObject\n{\n    var username:String = &quot;&quot;\n    var avatar:String = &quot;&quot;\n\n    init(name:String, logo:String)\n    {\n        self.username = name\n        self.avatar = logo\n    }\n}<\/code><\/pre>\n<h3>\u6d88\u606f\u4f53\u6570\u636e\u7ed3\u6784 MessageItem.swift<\/h3>\n<pre><code class=\"language-swift\">import UIKit\n\n\/\/\u6d88\u606f\u7c7b\u578b\uff0c\u6211\u7684\u8fd8\u662f\u522b\u4eba\u7684\nenum ChatType {\n    case mine\n    case someone\n}\n\nclass MessageItem {\n    \/\/\u7528\u6237\u4fe1\u606f\n    var user:UserInfo\n    \/\/\u6d88\u606f\u65f6\u95f4\n    var date:Date\n    \/\/\u6d88\u606f\u7c7b\u578b\n    var mtype:ChatType\n    \/\/\u5185\u5bb9\u89c6\u56fe\uff0c\u6807\u7b7e\u6216\u8005\u56fe\u7247\n    var view:UIView\n    \/\/\u8fb9\u8ddd\n    var insets:UIEdgeInsets\n\n    \/\/\u8bbe\u7f6e\u6211\u7684\u6587\u672c\u6d88\u606f\u8fb9\u8ddd\n    class func getTextInsetsMine() -&gt; UIEdgeInsets {\n        return UIEdgeInsets(top:9, left:10, bottom:9, right:17)\n    }\n\n    \/\/\u8bbe\u7f6e\u4ed6\u4eba\u7684\u6587\u672c\u6d88\u606f\u8fb9\u8ddd\n    class func getTextInsetsSomeone() -&gt; UIEdgeInsets {\n        return UIEdgeInsets(top:9, left:15, bottom:9, right:10)\n    }\n\n    \/\/\u8bbe\u7f6e\u6211\u7684\u56fe\u7247\u6d88\u606f\u8fb9\u8ddd\n    class func getImageInsetsMine() -&gt; UIEdgeInsets {\n        return UIEdgeInsets(top:9, left:10, bottom:9, right:17)\n    }\n\n    \/\/\u8bbe\u7f6e\u4ed6\u4eba\u7684\u56fe\u7247\u6d88\u606f\u8fb9\u8ddd\n    class func getImageInsetsSomeone() -&gt; UIEdgeInsets {\n        return UIEdgeInsets(top:9, left:15, bottom:9, right:10)\n    }\n\n    \/\/\u6784\u9020\u6587\u672c\u6d88\u606f\u4f53\n    convenience init(body:NSString, user:UserInfo, date:Date, mtype:ChatType) {\n        let font = UIFont.boldSystemFont(ofSize: 12)\n\n        let width = 225, height = 10000.0\n\n        let atts = [NSFontAttributeName: font]\n\n        let size = body.boundingRect(with:\n            CGSize(width: CGFloat(width), height: CGFloat(height)),\n            options: .usesLineFragmentOrigin, attributes:atts, context:nil)\n\n        let label = UILabel(frame:CGRect(x: 0, y: 0, width: size.size.width,\n                                          height: size.size.height))\n\n        label.numberOfLines = 0\n        label.lineBreakMode = NSLineBreakMode.byWordWrapping\n        label.text = (body.length != 0 ? body as String : &quot;&quot;)\n        label.font = font\n        label.backgroundColor = UIColor.clear\n\n        let insets:UIEdgeInsets = (mtype == ChatType.mine ?\n            MessageItem.getTextInsetsMine() : MessageItem.getTextInsetsSomeone())\n\n        self.init(user:user, date:date, mtype:mtype, view:label, insets:insets)\n    }\n\n    \/\/\u53ef\u4ee5\u4f20\u5165\u66f4\u591a\u7684\u81ea\u5b9a\u4e49\u89c6\u56fe\n    init(user:UserInfo, date:Date, mtype:ChatType, view:UIView, insets:UIEdgeInsets) {\n        self.view = view\n        self.user = user\n        self.date = date\n        self.mtype = mtype\n        self.insets = insets\n    }\n\n    \/\/\u6784\u9020\u56fe\u7247\u6d88\u606f\u4f53\n    convenience init(image:UIImage, user:UserInfo, date:Date, mtype:ChatType) {\n        var size = image.size\n        \/\/\u7b49\u6bd4\u7f29\u653e\n        if (size.width &gt; 220) {\n            size.height \/= (size.width \/ 220);\n            size.width = 220;\n        }\n        let imageView = UIImageView(frame:CGRect(x: 0, y: 0, width: size.width,\n                                                height: size.height))\n        imageView.image = image\n        imageView.layer.cornerRadius = 5.0\n        imageView.layer.masksToBounds = true\n\n        let insets:UIEdgeInsets = (mtype == ChatType.mine ?\n            MessageItem.getImageInsetsMine() : MessageItem.getImageInsetsSomeone())\n\n        self.init(user:user, date:date, mtype:mtype, view:imageView, insets:insets)\n    }\n}<\/code><\/pre>\n<h3>\u8868\u683c\u6570\u636e\u534f\u8bae ChatDataSource.swift<\/h3>\n<pre><code class=\"language-swift\">import Foundation\n\n\/*\n  \u6570\u636e\u63d0\u4f9b\u534f\u8bae\n*\/\nprotocol ChatDataSource\n{\n    \/*\u8fd4\u56de\u5bf9\u8bdd\u8bb0\u5f55\u4e2d\u7684\u5168\u90e8\u884c\u6570*\/\n    func rowsForChatTable( _ tableView:TableView) -&gt; Int\n    \/*\u8fd4\u56de\u67d0\u4e00\u884c\u7684\u5185\u5bb9*\/\n    func chatTableView(_ tableView:TableView, dataForRow:Int)-&gt; MessageItem\n}<\/code><\/pre>\n<h3>\u81ea\u5b9a\u4e49\u5355\u5143\u683c TableViewCell.swift<\/h3>\n<pre><code class=\"language-swift\">import UIKit\n\nclass TableViewCell: UITableViewCell {\n    \/\/\u6d88\u606f\u5185\u5bb9\u89c6\u56fe\n    var customView:UIView!\n    \/\/\u6d88\u606f\u80cc\u666f\n    var bubbleImage:UIImageView!\n    \/\/\u5934\u50cf\n    var avatarImage:UIImageView!\n    \/\/\u6d88\u606f\u6570\u636e\u7ed3\u6784\n    var msgItem:MessageItem!\n\n    required init?(coder aDecoder: NSCoder) {\n        super.init(coder: aDecoder)\n    }\n\n    \/\/- (void) setupInternalData\n    init(data:MessageItem, reuseIdentifier cellId:String) {\n        self.msgItem = data\n        super.init(style: UITableViewCellStyle.default, reuseIdentifier:cellId)\n        rebuildUserInterface()\n    }\n\n    func rebuildUserInterface() {\n\n        self.selectionStyle = UITableViewCellSelectionStyle.none\n        if (self.bubbleImage == nil)\n        {\n            self.bubbleImage = UIImageView()\n            self.addSubview(self.bubbleImage)\n        }\n\n        let type = self.msgItem.mtype\n        let width = self.msgItem.view.frame.size.width\n        let height = self.msgItem.view.frame.size.height\n\n        var x = (type == ChatType.someone) ? 0 : self.frame.size.width - width -\n            self.msgItem.insets.left - self.msgItem.insets.right\n\n        var y:CGFloat =  0\n        \/\/\u663e\u793a\u7528\u6237\u5934\u50cf\n        if (self.msgItem.user.username != &quot;&quot;)\n        {\n            let thisUser = self.msgItem.user\n            \/\/self.avatarImage.removeFromSuperview()\n\n            let imageName = thisUser.avatar != &quot;&quot; ? thisUser.avatar : &quot;noAvatar.png&quot;\n            self.avatarImage = UIImageView(image:UIImage(named:imageName))\n\n            self.avatarImage.layer.cornerRadius = 9.0\n            self.avatarImage.layer.masksToBounds = true\n            self.avatarImage.layer.borderColor = UIColor(white:0.0 ,alpha:0.2).cgColor\n            self.avatarImage.layer.borderWidth = 1.0\n\n            \/\/\u522b\u4eba\u5934\u50cf\uff0c\u5728\u5de6\u8fb9\uff0c\u6211\u7684\u5934\u50cf\u5728\u53f3\u8fb9\n            let avatarX = (type == ChatType.someone) ? 2 : self.frame.size.width - 52\n\n            \/\/\u5934\u50cf\u5c45\u4e8e\u6d88\u606f\u9876\u90e8\n            let avatarY:CGFloat = 0\n            \/\/set the frame correctly\n            self.avatarImage.frame = CGRect(x: avatarX, y: avatarY, width: 50, height: 50)\n            self.addSubview(self.avatarImage)\n\n            \/\/\u5982\u679c\u53ea\u6709\u4e00\u884c\u6d88\u606f\uff08\u6d88\u606f\u6846\u9ad8\u5ea6\u4e0d\u5927\u4e8e\u5934\u50cf\uff09\u5219\u5c06\u6d88\u606f\u6846\u5c45\u4e2d\u4e8e\u5934\u50cf\u4f4d\u7f6e \n            let delta = (50 - (self.msgItem.insets.top\n                + self.msgItem.insets.bottom + self.msgItem.view.frame.size.height))\/2\n            if (delta &gt; 0) {\n                y = delta\n            }\n            if (type == ChatType.someone) {\n                x += 54\n            }\n            if (type == ChatType.mine) {\n                x -= 54\n            }\n        }\n\n        self.customView = self.msgItem.view\n        self.customView.frame = CGRect(x: x + self.msgItem.insets.left,\n            y: y + self.msgItem.insets.top, width: width, height: height)\n\n        self.addSubview(self.customView)\n\n        \/\/\u5982\u679c\u662f\u522b\u4eba\u7684\u6d88\u606f\uff0c\u5728\u5de6\u8fb9\uff0c\u5982\u679c\u662f\u6211\u8f93\u5165\u7684\u6d88\u606f\uff0c\u5728\u53f3\u8fb9\n        if (type == ChatType.someone) {\n            self.bubbleImage.image = UIImage(named:(&quot;left_bubble.png&quot;))!\n                .stretchableImage(withLeftCapWidth: 21, topCapHeight:25)\n        } else {\n            self.bubbleImage.image = UIImage(named:&quot;right_bubble.png&quot;)!\n                .stretchableImage(withLeftCapWidth: 15, topCapHeight:25)\n        }\n        self.bubbleImage.frame = CGRect(x: x, y: y,\n            width: width + self.msgItem.insets.left + self.msgItem.insets.right,\n            height: height + self.msgItem.insets.top + self.msgItem.insets.bottom)\n    }\n\n    \/\/\u8ba9\u5355\u5143\u683c\u5bbd\u5ea6\u59cb\u7ec8\u4e3a\u5c4f\u5e55\u5bbd\n    override var frame: CGRect {\n        get {\n            return super.frame\n        }\n        set (newFrame) {\n            var frame = newFrame\n            frame.size.width = UIScreen.main.bounds.width\n            super.frame = frame\n        }\n    }\n}<\/code><\/pre>\n<h3>\u81ea\u5b9a\u4e49\u5355\u5143\u683c\u5934\u90e8 TableHeaderViewCell.swift<\/h3>\n<pre><code class=\"language-swift\">import UIKit\n\nclass TableHeaderViewCell: UITableViewCell\n{\n    var height:CGFloat = 30.0\n    var label:UILabel!\n\n    required init?(coder aDecoder: NSCoder) {\n        super.init(coder: aDecoder)\n    }\n\n    init(reuseIdentifier cellId:String)\n    {\n        super.init(style: UITableViewCellStyle.default, reuseIdentifier:cellId)\n    }\n\n    class func getHeight() -&gt; CGFloat\n    {\n        return 30.0\n    }\n\n    func setDate(_ value:Date)\n    {\n        self.height = 30.0\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = &quot;yyyy\u5e74MM\u6708dd\u65e5&quot;\n        let text = dateFormatter.string(from: value)\n\n        if (self.label != nil)\n        {\n            self.label.text = text\n            return\n        }\n        self.selectionStyle = UITableViewCellSelectionStyle.none\n        self.label = UILabel(frame:CGRect(x: CGFloat(0), y: CGFloat(0), width: self.frame.size.width, height: height))\n\n        self.label.text = text\n        self.label.font = UIFont.boldSystemFont(ofSize: 12)\n\n        self.label.textAlignment = NSTextAlignment.center\n        self.label.shadowOffset = CGSize(width: 0, height: 1)\n        self.label.shadowColor = UIColor.white\n\n        self.label.textColor = UIColor.darkGray\n\n        self.label.backgroundColor = UIColor.clear\n\n        self.addSubview(self.label)\n    }\n}<\/code><\/pre>\n<h3>\u81ea\u5b9a\u4e49\u8868\u683c TableView.swift<\/h3>\n<pre><code class=\"language-swift\">import UIKit\n\nenum ChatBubbleTypingType\n{\n    case nobody\n    case me\n    case somebody\n}\n\nclass TableView: UITableView, UITableViewDelegate, UITableViewDataSource\n{\n    \/\/\u7528\u4e8e\u4fdd\u5b58\u6240\u6709\u6d88\u606f\n    var bubbleSection:NSMutableArray!\n    \/\/\u6570\u636e\u6e90\uff0c\u7528\u4e8e\u4e0e ViewController \u4ea4\u6362\u6570\u636e\n    var chatDataSource:ChatDataSource!\n\n    var snapInterval:TimeInterval!\n    var typingBubble:ChatBubbleTypingType!\n\n    required init?(coder aDecoder: NSCoder) {\n        super.init(coder: aDecoder)\n    }\n\n    override init(frame: CGRect, style: UITableViewStyle) {\n        \/\/the snap interval in seconds implements a headerview to seperate chats\n        self.snapInterval = TimeInterval(60 * 60 * 24) \/\/one day\n        self.typingBubble = ChatBubbleTypingType.nobody\n        self.bubbleSection = NSMutableArray()\n\n        super.init(frame:frame,  style:style)\n\n        self.backgroundColor = UIColor.clear\n        self.separatorStyle = UITableViewCellSeparatorStyle.none\n        self.delegate = self\n        self.dataSource = self\n    }\n\n    override func reloadData()\n    {\n        self.showsVerticalScrollIndicator = false\n        self.showsHorizontalScrollIndicator = false\n        self.bubbleSection = NSMutableArray()\n        var count = 0\n        if ((self.chatDataSource != nil))\n        {\n            count = self.chatDataSource.rowsForChatTable(self)\n\n            if(count &gt; 0)\n            {\n                let bubbleData =  NSMutableArray(capacity:count)\n\n                for i in 0 ..&lt; count\n                {\n                    let object =  self.chatDataSource.chatTableView(self, dataForRow:i)\n                    bubbleData.add(object)\n                }\n                bubbleData.sort(comparator: sortDate)\n\n                var last =  &quot;&quot;\n\n                var currentSection = NSMutableArray()\n                \/\/ \u521b\u5efa\u4e00\u4e2a\u65e5\u671f\u683c\u5f0f\u5668\n                let dformatter = DateFormatter()\n                \/\/ \u4e3a\u65e5\u671f\u683c\u5f0f\u5668\u8bbe\u7f6e\u683c\u5f0f\u5b57\u7b26\u4e32\n                dformatter.dateFormat = &quot;dd&quot;\n\n                for i in 0 ..&lt; count\n                {\n                    let data =  bubbleData[i] as! MessageItem\n                    \/\/ \u4f7f\u7528\u65e5\u671f\u683c\u5f0f\u5668\u683c\u5f0f\u5316\u65e5\u671f\uff0c\u65e5\u671f\u4e0d\u540c\uff0c\u5c31\u65b0\u5206\u7ec4\n                    let datestr = dformatter.string(from: data.date as Date)\n                    if (datestr != last)\n                    {\n                        currentSection = NSMutableArray()\n                        self.bubbleSection.add(currentSection)\n                    }\n                    (self.bubbleSection[self.bubbleSection.count-1] as AnyObject).add(data)\n\n                    last = datestr\n                }\n            }\n        }\n        super.reloadData()\n\n        \/\/\u6ed1\u5411\u6700\u540e\u4e00\u90e8\u5206\n        let secno = self.bubbleSection.count - 1\n        let indexPath =  IndexPath(row:(self.bubbleSection[secno] as AnyObject).count,section:secno)\n\n        self.scrollToRow(at: indexPath,                at:UITableViewScrollPosition.bottom,animated:true)\n    }\n\n    \/\/\u6309\u65e5\u671f\u6392\u5e8f\u65b9\u6cd5\n    func sortDate(_ m1: Any, m2: Any) -&gt; ComparisonResult {\n        if((m1 as! MessageItem).date.timeIntervalSince1970 &lt; (m2 as! MessageItem).date.timeIntervalSince1970)\n        {\n            return ComparisonResult.orderedAscending\n        }\n        else\n        {\n            return ComparisonResult.orderedDescending\n        }\n    }\n\n    \/\/\u7b2c\u4e00\u4e2a\u65b9\u6cd5\u8fd4\u56de\u5206\u533a\u6570\n    func numberOfSections(in tableView:UITableView)-&gt;Int {\n        var result = self.bubbleSection.count\n        if (self.typingBubble != ChatBubbleTypingType.nobody)\n        {\n            result += 1;\n        }\n        return result;\n    }\n\n    \/\/\u8fd4\u56de\u6307\u5b9a\u5206\u533a\u7684\u884c\u6570\n    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {\n        if (section &gt;= self.bubbleSection.count)\n        {\n            return 1\n        }\n\n        return (self.bubbleSection[section] as AnyObject).count + 1\n    }\n\n    \/\/\u7528\u4e8e\u786e\u5b9a\u5355\u5143\u683c\u7684\u9ad8\u5ea6\uff0c\u5982\u679c\u6b64\u65b9\u6cd5\u5b9e\u73b0\u5f97\u4e0d\u5bf9\uff0c\u5355\u5143\u683c\u4e0e\u5355\u5143\u683c\u4e4b\u95f4\u4f1a\u9519\u4f4d\n    func tableView(_ tableView:UITableView, heightForRowAt indexPath:IndexPath)\n        -&gt; CGFloat {\n        \/\/ Header\n        if (indexPath.row == 0)\n        {\n            return TableHeaderViewCell.getHeight()\n        }\n        let section = self.bubbleSection[indexPath.section] as! NSMutableArray\n        let data = section[indexPath.row - 1]\n\n        let item =  data as! MessageItem\n        let height  =  max(item.insets.top + item.view.frame.size.height  + item.insets.bottom, 52) + 17\n        print(&quot;height:\\(height)&quot;)\n        return height\n    }\n\n    \/\/\u8fd4\u56de\u81ea\u5b9a\u4e49\u7684 TableViewCell\n    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)\n        -&gt; UITableViewCell {        \n        \/\/ Header based on snapInterval\n        if (indexPath.row == 0)\n        {\n            let cellId = &quot;HeaderCell&quot;\n\n            let hcell = TableHeaderViewCell(reuseIdentifier:cellId)\n            let section = self.bubbleSection[indexPath.section] as! NSMutableArray\n            let data = section[indexPath.row] as! MessageItem\n\n            hcell.setDate(data.date)\n            return hcell\n        }\n        \/\/ Standard\n        let cellId = &quot;ChatCell&quot;\n\n        let section = self.bubbleSection[indexPath.section] as! NSMutableArray\n        let data = section[indexPath.row - 1]\n\n        let cell = TableViewCell(data:data as! MessageItem, reuseIdentifier:cellId)\n\n        return cell\n    }\n}<\/code><\/pre>\n<h2>\u5f15\u7528\u7d20\u6750<\/h2>\n<p><img decoding=\"async\" src=\"http:\/\/www.yezhou.me\/AppBlog\/images\/iOS\/left_bubble@2x.png\" alt=\"left_bubble\" \/><\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.yezhou.me\/AppBlog\/images\/iOS\/right_bubble@2x.png\" alt=\"right_bubble\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u6ce8\uff1a\u4ee3\u7801\u5df2\u5347\u7ea7\u81f3Swift4 \u8bbe\u8ba1\u9700\u6c42 \u5fae\u4fe1\u804a\u5929\u754c\u9762\u7684\u6d88\u606f\u5c55\u793a\u5217\u8868\uff0c\u5b9e\u73b0\u7684\u529f\u80fd\u6709\uff1a \uff081\uff09\u6d88\u606f\u53ef\u4ee5\u662f\u6587\u672c\u6d88\u606f\u4e5f [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[171],"tags":[],"class_list":["post-596","post","type-post","status-publish","format-standard","hentry","category-swift"],"_links":{"self":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/596","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/comments?post=596"}],"version-history":[{"count":0,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/596\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/media?parent=596"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/categories?post=596"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/tags?post=596"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}