博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Swift - 自定义单元格实现微信聊天界面
阅读量:5253 次
发布时间:2019-06-14

本文共 10749 字,大约阅读时间需要 35 分钟。

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,源码下载: 

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

   

转载于:https://www.cnblogs.com/Free-Thinker/p/4838230.html

你可能感兴趣的文章
android 签名
查看>>
vue项目中使用百度统计
查看>>
android:scaleType属性
查看>>
SuperEPC
查看>>
mysql-5.7 innodb 的并行任务调度详解
查看>>
shell脚本
查看>>
Upload Image to .NET Core 2.1 API
查看>>
Js时间处理
查看>>
Java项目xml相关配置
查看>>
三维变换概述
查看>>
第三次作业
查看>>
vue route 跳转
查看>>
【雷电】源代码分析(二)-- 进入游戏攻击
查看>>
Entityframework:“System.Data.Entity.Internal.AppConfig”的类型初始值设定项引发异常。...
查看>>
Linux中防火墙centos
查看>>
mysql新建用户,用户授权,删除用户,修改密码
查看>>
FancyCoverFlow
查看>>
JS博客
查看>>
如何设置映射网络驱动器的具体步骤和方法
查看>>
ASP.NET WebApi 基于OAuth2.0实现Token签名认证
查看>>