Swift - 自定义单元格实现微信聊天界面

1,下面是一个放微信聊天界面的消息展示列表,实现的功能有:

(1)消息可以是文本消息也可以是图片消息
(2)消息背景为气泡状图片,同时消息气泡可根据内容自适应大小
(3)每条消息旁边有头像,在左边表示发送方,在右边表示接收方

2,实现思路
(1)需要定义一个数据结构保存消息内容 MessageItem
(2)继承UITableViewCell实现自定义单元格,这里面放入头像和消息体
(3)继承UITableView实现自定义表格,通过读取数据源,进行页面的渲染
(4)消息体根据内容类型不同,用不同的展示方法
(5)每个单元格的高度需要根据内容计算出来
(6)数据由ViewController来提供初始化数据

3,效果图


4,代码结构

5,主要代码
(1)主页面 ViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import UIKit
class ViewController: UIViewController, ChatDataSource {
     
    var Chats:Array<MessageItem>!
    var tableView:TableView!
     
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
         
        setupChatTable()
    }
     
    /*创建表格及数据*/
    func setupChatTable()
    {
        self.tableView = TableView(frame:CGRectMake(0, 20,
            self.view.frame.size.width, self.view.frame.size.height - 20))
         
        //创建一个重用的单元格
        self.tableView!.registerClass(TableViewCell.self, forCellReuseIdentifier: "MsgCell")
         
        var me = "xiaoming.png"
         
        var you = "xiaohua.png"
         
        var first =  MessageItem(body:"嘿,这张照片咋样,我周末拍的呢!", logo:me,
            date:NSDate(timeIntervalSinceNow:-600), mtype:ChatType.Mine)
         
         
        var second =  MessageItem(image:UIImage(named:"luguhu.jpeg")!,logo:me,
            date:NSDate(timeIntervalSinceNow:-290), mtype:ChatType.Mine)
         
        var third =  MessageItem(body:"太赞了,我也想去那看看呢!",logo:you,
            date:NSDate(timeIntervalSinceNow:-60), mtype:ChatType.Someone)
         
         var fouth =  MessageItem(body:"嗯,下次我们一起去吧!",logo:me,
            date:NSDate(timeIntervalSinceNow:-20), mtype:ChatType.Mine)
         
        var fifth =  MessageItem(body:"好的,一定!",logo:you,
            date:NSDate(timeIntervalSinceNow:0), mtype:ChatType.Someone)
         
        Chats = [first,second, third, fouth, fifth]
         
        self.tableView.chatDataSource = self
         
     
        self.tableView.reloadData()
         
        self.view.addSubview(self.tableView)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    /*返回对话记录中的全部行数*/
    func rowsForChatTable(tableView:TableView) -> Int
    {
        return self.Chats.count
    }
     
    /*返回某一行的内容*/
    func chatTableView(tableView:TableView, dataForRow row:Int) -> MessageItem
    {
        return Chats[row]
    }
}


(2)消息体数据结构 MessageItem.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import UIKit
//消息类型,我的还是别人的
enum ChatType
{
    case Mine
    case Someone
}
class MessageItem
{
    //头像
    var logo:String
    //消息时间
    var date:NSDate
    //消息类型
    var mtype:ChatType
    //内容视图,标签或者图片
    var view:UIView
    //边距
    var insets:UIEdgeInsets
     
    //设置我的文本消息边距
    class func getTextInsetsMine() -> UIEdgeInsets
    {
        return UIEdgeInsets(top:5, left:10, bottom:11, right:17)
    }
     
    //设置他人的文本消息边距
    class func getTextInsetsSomeone() -> UIEdgeInsets
    {
        return UIEdgeInsets(top:5, left:15, bottom:11, right:10)
    }
     
    //设置我的图片消息边距
    class func getImageInsetsMine() -> UIEdgeInsets
    {
        return UIEdgeInsets(top:11, left:13, bottom:16, right:22)
    }
     
    //设置他人的图片消息边距
    class func getImageInsetsSomeone() -> UIEdgeInsets
    {
        return UIEdgeInsets(top:11, left:13, bottom:16, right:22)
    }
     
    //构造文本消息体
    convenience init(body:NSString, logo:String, date:NSDate, mtype:ChatType)
    {
        var font =  UIFont.boldSystemFontOfSize(12)
         
        var width =  225, height = 10000.0
         
        var atts =  NSMutableDictionary()
        atts.setObject(font,forKey:NSFontAttributeName)
         
        var size =  body.boundingRectWithSize(CGSizeMake(CGFloat(width), CGFloat(height)),
            options:NSStringDrawingOptions.UsesLineFragmentOrigin, attributes:atts, context:nil)
         
        var label =  UILabel(frame:CGRectMake(0, 0, size.size.width, size.size.height))
         
        label.numberOfLines = 0
        label.lineBreakMode = NSLineBreakMode.ByWordWrapping
        label.text = (body.length != 0 ? body : "")
        label.font = font
        label.backgroundColor = UIColor.clearColor()
         
        var insets:UIEdgeInsets =  (mtype == ChatType.Mine ?
            MessageItem.getTextInsetsMine() : MessageItem.getTextInsetsSomeone())
         
        self.init(logo:logo, date:date, mtype:mtype, view:label, insets:insets)
    }
     
    //可以传入更多的自定义视图
    init(logo:String, date:NSDate, mtype:ChatType, view:UIView, insets:UIEdgeInsets)
    {
        self.view = view
        self.logo = logo
        self.date = date
        self.mtype = mtype
        self.insets = insets
    }
     
    //构造图片消息体
    convenience init(image:UIImage, logo:String,  date:NSDate, mtype:ChatType)
    {
        var size = image.size
        //等比缩放
        if (size.width > 220)
        {
            size.height /= (size.width / 220);
            size.width = 220;
        }
        var imageView = UIImageView(frame:CGRectMake(0, 0, size.width, size.height))
        imageView.image = image
        imageView.layer.cornerRadius = 5.0
        imageView.layer.masksToBounds = true
         
        var insets:UIEdgeInsets =  (mtype == ChatType.Mine ?
            MessageItem.getImageInsetsMine() : MessageItem.getImageInsetsSomeone())
         
        self.init(logo:logo,  date:date, mtype:mtype, view:imageView, insets:insets)
    }   
}


(3)表格数据协议 ChatDataSource.swift

1
2
3
4
5
6
7
8
9
10
11
12
import Foundation
/*
  数据提供协议
*/
protocol ChatDataSource
{  
    /*返回对话记录中的全部行数*/
    func rowsForChatTable( tableView:TableView) -> Int
    /*返回某一行的内容*/
    func chatTableView(tableView:TableView, dataForRow:Int)-> MessageItem
}


(4)自定义表格 TableView.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import UIKit
class TableView:UITableView,UITableViewDelegate, UITableViewDataSource
{
    //用于保存所有消息
    var bubbleSection:Array<MessageItem>!
    //数据源,用于与 ViewController 交换数据
    var chatDataSource:ChatDataSource!
     
    required init(coder aDecoder: NSCoder) {
        
        super.init(coder: aDecoder)
    }
     
    override init(frame:CGRect)
    {
        self.bubbleSection = Array<MessageItem>()
         
        super.init(frame:frame,  style:UITableViewStyle.Grouped)
         
        self.backgroundColor = UIColor.clearColor()
         
        self.separatorStyle = UITableViewCellSeparatorStyle.None
        self.delegate = self
        self.dataSource = self
         
         
    }
     
    override func reloadData()
    {
         
        self.showsVerticalScrollIndicator = false
        self.showsHorizontalScrollIndicator = false
         
        var count =  0
        if ((self.chatDataSource != nil))
        {
            count = self.chatDataSource.rowsForChatTable(self)
             
            if(count > 0)
            {  
                 
                for (var i = 0; i < count; i++)
                {
                     
                    var object =  self.chatDataSource.chatTableView(self, dataForRow:i)
                    bubbleSection.append(object)
                     
                }
                 
                //按日期排序方法
                bubbleSection.sort({$0.date.timeIntervalSince1970 < $1.date.timeIntervalSince1970})
            }
        }
        super.reloadData()
    }
     
    //第一个方法返回分区数,在本例中,就是1
    func numberOfSectionsInTableView(tableView:UITableView)->Int
    {
        return 1
    }
     
    //返回指定分区的行数
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        if (section >= self.bubbleSection.count)
        {
            return 1
        }
         
        return self.bubbleSection.count+1
    }
         
    //用于确定单元格的高度,如果此方法实现得不对,单元格与单元格之间会错位
    func tableView(tableView:UITableView,heightForRowAtIndexPath indexPath:NSIndexPath) -> CGFloat
    {
         
        // Header
        if (indexPath.row == 0)
        {
            return 30.0
        }
         
        var data =  self.bubbleSection[indexPath.row - 1]
         
        return max(data.insets.top + data.view.frame.size.height + data.insets.bottom, 52)
    }
     
    //返回自定义的 TableViewCell
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
        -> UITableViewCell
    {
           
        var cellId = "MsgCell"
        if(indexPath.row > 0)
        {
            var data =  self.bubbleSection[indexPath.row-1]
         
            var cell =  TableViewCell(data:data, reuseIdentifier:cellId)
         
            return cell
        }
        else
        {
             
            return UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellId)
        }
    }
}


(5)自定义单元格 TableViewCell.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import UIKit
class TableViewCell:UITableViewCell
{
    //消息内容视图
    var customView:UIView!
    //消息背景
    var bubbleImage:UIImageView!
    //头像
    var avatarImage:UIImageView!
    //消息数据结构
    var msgItem:MessageItem!
     
    required init(coder aDecoder: NSCoder) {
         
        super.init(coder: aDecoder)
    }
     
    //- (void) setupInternalData
    init(data:MessageItem, reuseIdentifier cellId:String)
    {
        self.msgItem = data
        super.init(style: UITableViewCellStyle.Default, reuseIdentifier:cellId)
        rebuildUserInterface()
    }
     
    func rebuildUserInterface()
    {
         
        self.selectionStyle = UITableViewCellSelectionStyle.None
        if (self.bubbleImage == nil)
        {
            self.bubbleImage = UIImageView()
            self.addSubview(self.bubbleImage)
             
        }
         
        var type =  self.msgItem.mtype
        var width =  self.msgItem.view.frame.size.width
         
        var height =  self.msgItem.view.frame.size.height
         
        var x =  (type == ChatType.Someone) ? 0 : self.frame.size.width - width -
            self.msgItem.insets.left - self.msgItem.insets.right
         
        var y:CGFloat =  0
        //显示用户头像
        if (self.msgItem.logo != "")
        {
             
            var logo =  self.msgItem.logo
             
            self.avatarImage = UIImageView(image:UIImage(named:(logo != "" ? logo : "noAvatar.png")))
             
            self.avatarImage.layer.cornerRadius = 9.0
            self.avatarImage.layer.masksToBounds = true
            self.avatarImage.layer.borderColor = UIColor(white:0.0 ,alpha:0.2).CGColor
            self.avatarImage.layer.borderWidth = 1.0
             
            //别人头像,在左边,我的头像在右边
            var avatarX =  (type == ChatType.Someone) ? 2 : self.frame.size.width - 52
             
            //头像居于消息底部
            var avatarY =  height
            //set the frame correctly
            self.avatarImage.frame = CGRectMake(avatarX, avatarY, 50, 50)
            self.addSubview(self.avatarImage)
             
             
            var delta =  self.frame.size.height - (self.msgItem.insets.top + self.msgItem.insets.bottom
                + self.msgItem.view.frame.size.height)
            if (delta > 0)
            {
                y = delta
            }
            if (type == ChatType.Someone)
            {
                x += 54
            }
            if (type == ChatType.Mine)
            {
                x -= 54
            }
        }
         
        self.customView = self.msgItem.view
        self.customView.frame = CGRectMake(x + self.msgItem.insets.left, y
            + self.msgItem.insets.top, width, height)
         
        self.addSubview(self.customView)
         
        //如果是别人的消息,在左边,如果是我输入的消息,在右边
        if (type == ChatType.Someone)
        {
            self.bubbleImage.image =
                UIImage(named:("yoububble.png"))!.stretchableImageWithLeftCapWidth(21,topCapHeight:14)
             
        }
        else {
            self.bubbleImage.image =
                UIImage(named:"mebubble.png")!.stretchableImageWithLeftCapWidth(15, topCapHeight:14)
        }
        self.bubbleImage.frame = CGRectMake(x, y, width + self.msgItem.insets.left
            + self.msgItem.insets.right, height + self.msgItem.insets.top + self.msgItem.insets.bottom)
    }
}


6,源码下载:WeiXinChart.zip 


7,功能改进版下载:WeiXinChart_advance.zip
(1)消息按天分组展示
(2)增加消息发送框,可以发送和展示消息

   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/287502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[python opencv 计算机视觉零基础到实战] 十三 直方图颜色提鲜

一、学习目标 了解了均衡化的作用是什么了解灰度、YUV、彩色图片均衡化的方法是使用什么方法了解了合并通道的方法是什么了解了分离通道的方法是什么 如有错误欢迎指出~ 二、了解图像均衡化 2.1 了解直方图均衡化 图像直方图均衡化主要是对图像中的少数灰度进行压缩&#…

java 中字符串比较方法_java中常用的字符串的比较方法(两种)

比较字符串比较常用的两个方法是运算符“”和String的equals方法。使用“”比较两个字符串&#xff0c;是比较两个对象的的“地址”是否一致&#xff0c;本质就是判断两个变量是否指向同一个对象&#xff0c;如果是则返回true&#xff0c;否则返回的是false。而String类的equal…

分布式服务框架dubbo原理解析 转

alibaba有好几个分布式框架&#xff0c;主要有&#xff1a;进行远程调用(类似于RMI的这种远程调用)的(dubbo、hsf)&#xff0c;jms消息服务(napoli、notify)&#xff0c;KV数据库(tair)等。这个框架/工具/产品在实现的时候&#xff0c;都考虑到了容灾&#xff0c;扩展&#xff…

[python opencv 计算机视觉零基础到实战] 十五 直方图反向投影

一、学习目标 了解了直方图反向投影的一般流程了解2D直方图的使用 如有错误欢迎指出~ 二、了解直方图反向投影 2.1 了解2D直方图 需要对直方图进行反向投影&#xff0c;需要使用2D直方图。2D直方图需要使用calcHist方法。calcHist方法在前两节中已经有了解&#xff0c;现在…

关联规则java代码_重量挖掘关联规则挖掘方法,哪个大神可以将以下伪代码转换为Java代码?...

重量挖掘关联规则挖掘方法&#xff0c;哪个大神可以将以下伪代码转换为Java代码&#xff1f; 10改进的加权关联规则算法的基本步骤与Apriori算法相似: 首先找到加权支持度不小于用户指定的最小加权支持度的所有频繁项集加权关联规则&#xff0c;然后使用频繁项集生成所有满足最…

多种语言《九九乘法表》荟萃:C、C++、C#、JavaScript、SQL、VB、VBA、Python

九九乘法表对于我们学习循环结构,尤其是双重循环特别有帮助,本文演示用C、C++、C#、HTML、SQL、VB、VBA、Python等多种语九九乘法表。 一、C语言 #include<stdio.h> main() {int i,j;for(i=1;i<=9;i++){for(j=1;j<=i;j++){printf("%d*%d=%d\t",j,i,i*j…

[python opencv 计算机视觉零基础到实战] 十六、用opencv画画

一、学习目标 了解如何使用line方法了解如何使用rectangle方法了解如何使用ellipse方法 如有错误欢迎指出~ 二、了解OpenCV的绘图方法 2.1 了解直线绘图方法 我们在前两节中有了解使用OpenCV中的矩形绘制&#xff0c;接下来我们了解一下更多的图形绘制方法。我们在OpenCV中…

外部中断0(含知识点)

1 #include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>2 #include "stm32f10x_gpio.h"3 #include "stm32f10x_exti.h"4 #include "misc.h"5 6 /*外部中断配置*/7 8 9 int main(void)10 {11 /*************…

[python opencv 计算机视觉零基础到实战] 十七、用鼠标画画

一、学习目标 了解长轴和短轴参数了解旋转角度参数了解起始角度参数了解终止角度参数 二、深入了解OpenCV的ellipse方法 2.1 纵横 椭圆的绘制方法在上一节中我们已经知道了是使用ellipse&#xff0c;ellipse的函数原型如下&#xff0c;为了清晰认识ellipse方法&#xff0c;…

Microsoft Build 2022 到来,开发者们做好准备了吗?

Microsoft Build 2022 将会在5月24日 - 5月26日正式举行&#xff0c;作为开发者的你是否和我一样期待本次的 Build&#xff0c;它会带来什么的技术革新以及黑科技呢&#xff1f;下面我就针对今年 Build 的几个主题结合相关技术来谈谈我对 Build 的期待。开发技术和相关工具今年…

【测绘程序设计】视距测量神器V1.0(附源程序)

神器预览: 一、视距测量概念 视距测量是利用水准仪的望远镜内十字丝分划板上的视距丝在视距尺(水准尺)上读数,根据光学和几何学原理,同时测定仪器到地面点的水平距离和高差的一种方法。 视距测量具有操作简便、速度快、不受地面起伏变化的影响的优点,被广泛应用于碎部测…

Android Studio之查找当前类的位置图标没了

1 问题 我们知道在Android studio里面如果想快速看到该文件所在的目录位置&#xff0c;点击这个图标 现在这个图标没了&#xff0c;找不到了 2 解决办法 把下面的Autoscroll from Source的勾去掉就行。

[python opencv 计算机视觉零基础到实战] 十八、用鼠标进行画画

一、学习目标 了解如何在图片中加入文字了解如何使用鼠标进行图像绘制 二、了解如何通过鼠标进行图像绘制 2.1 了解putText方法的使用 putText方法接收图像&#xff0c;文字内容&#xff0c; 坐标 &#xff0c;字体&#xff0c;大小&#xff0c;颜色&#xff0c;字体厚度这…

【测绘程序设计】坐标正算神器V1.0(附C/C#/VB源程序)

坐标正算,就是根据直线的边长、坐标方位角和一个端点的坐标,计算直线另一个端点的坐标的工作。本文用C#语言和VB语言实现坐标正算,编写坐标正算神器。 计算实例: 实例1,设直线AB的边长DAB和一个端点A的坐标XA、YA为已知,则直线另一个端点B的坐标为: XB=XA+ΔXAB YB=YA+…

Blazor University (22)路由 —— 定义路由

原文链接&#xff1a;https://blazor-university.com/routing/路由与标准 ASP.NET MVC 一样&#xff0c;Blazor 路由是一种用于检查浏览器 URL 并将其匹配到要呈现的页面的技术。路由比简单地将 URL 匹配到页面更灵活。它允许我们根据文本模式进行匹配&#xff0c;例如&#xf…

Linux文件I/O编程(二)lseek函数

文件I/O编程处理open、read、write、close&#xff0c;等必要函数对文件进行读写操作外&#xff0c;lseek、fcntl也是I/O编程很重要的函数。lseek函数lseek函数主要用来移动当前读写位置&#xff0c;第一个参数是文件描述符fd&#xff0c;第二个参数是偏移距离&#xff0c;第三…

[python opencv 计算机视觉零基础到实战] 十九、简易绘画板制作

一、学习目标 了解事件编写一个简易绘画板 二、了解如何制作简易绘画板 2.1 了解鼠标多种事件 上一节我们简单的使用opencv的图形绘制方法&#xff0c;用鼠标绘制了一些内容。上一节所响应的是简单的双击事件EVENT_LBUTTONDBLCLK&#xff0c;在OpenCV的鼠标事件中还有很多。…

Web App 和 Native App,哪个是趋势?

2019独角兽企业重金招聘Python工程师标准>>> Web App 和 Native App&#xff0c;哪个是趋势&#xff1f; https://www.zhihu.com/question/19558750 WebAPP与原生APP的交互设计区别 http://www.woshipm.com/ucd/132869.html Web App 开发使用哪种框架比较好&#xf…

基于天地图的应用服务系统设计开发—以甘肃高校招生服务为例

“天地图”是国家测绘地理信息局建设的地理信息综合服务网站。它是“数字中国”的重要组成部分,是国家地理信息公共服务平台的公众版。“天地图”的目的在于促进地理信息资源共享和高效利用,提高测绘地理信息公共服务能力和水平,改进测绘地理信息成果的服务方式,更好地满足…