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)增加消息发送框,可以发送和展示消息

Swift - 自定义单元格实现微信聊天界面的更多相关文章

  1. 浅谈DevExpress<五>:TreeList简单的美化——自定义单元格,加注释以及行序号

    今天就以昨天的列表为例,实现以下效果:预算大于110万的单元格突出显示,加上行序号以及注释,如下图:

  2. android 仿微信聊天界面,以及语音录制功能

    extends:http://104zz.iteye.com/blog/1709840 本例为模仿微信聊天界面UI设计,文字发送以及语言录制UI. 1先看效果图:     第一:chat.xml设计 ...

  3. HTML5仿手机微信聊天界面

    HTML5仿手机微信聊天界面 这篇文章主要为大家详细介绍了HTML5仿手机微信聊天界面的关键代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下     给大家带来的是HTML5仿手机微信聊天界面, ...

  4. jQuery MiniUI自定义单元格

    监听处理"drawcell"事件 使用"drawcell"事件,可以自定义单元格内容.样式.行样式等. grid.on("drawcell" ...

  5. Android:日常学习笔记(8)———开发微信聊天界面

    Android:日常学习笔记(8)———开发微信聊天界面 只做Nine-Patch图片 Nine-Patch是一种被特殊处理过的PNG图片,能够指定哪些区域可以被拉升,哪些区域不可以.

  6. 使用VUE组件创建SpreadJS自定义单元格(一)

    作为近五年都冲在热门框架排行榜首的Vue,大家一定会学到的一部分就是组件的使用.前端开发的模块化,可以让代码逻辑更加简单清晰,项目的扩展性大大加强.对于Vue而言,模块化的体现集中在组件之上,以组件为 ...

  7. 使用VUE组件创建SpreadJS自定义单元格(二)

    在上篇中,我们介绍了如何通过设置runtimeCompiler为true,在Vue中实现了动态创建电子表格组件.想了解具体内容可看点击查看使用VUE组件创建SpreadJS自定义单元格(一). 但是在 ...

  8. 剖析:如何用 SwitchUI 5天写一个微信 —— 聊天界面篇

    前置资源 GitHub: SwiftUI-WeChatDemo 第零章:用 SwiftUI 五天组装一个微信 - wavky - 博客园 整体结构 UI 部分代码分布如上图所示,App 的主入口类为 ...

  9. NPOI 自定义单元格背景颜色-Excel

    NPOI针对office2003使用HSSFWorkbook,对于offce2007及以上使用XSSFWorkbook:今天我以HSSFWorkbook自定义颜色为例说明,Office2007的未研究 ...

随机推荐

  1. BZOJ 2073: [POI2004]PRZ( 状压dp )

    早上这道题没调完就去玩NOI网络同步赛了.... 状压dp , dp( s ) 表示 s 状态下所用的最短时间 , 转移就直接暴力枚举子集 . 可以先预处理出每个状态下的重量和时间的信息 . 复杂度是 ...

  2. aop编程 环绕round

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  3. 【转】增强 scite 编辑器的代码提示功能

    在 windows 下写 Lua, 我能找到的最好的编辑器就是 luaForWindows 项目里带的 scite. npp (即 notepad++ ) 也将就着能用, 不过只有代码高亮和简单的单词 ...

  4. NSString和NSDate的转换

    输入的日期字符串形如:@"1992-05-21 13:08:08" - (NSDate *)dateFromString:(NSString *)dateString{ NSDat ...

  5. mysql数据库修改数据库编码,字段编码与表编码(转) good

    最近看了一下KIGG框架,发现在用EF + MYSQL数据库时,数据出现了乱码,用工具(Navicat )查看一下表category的结构发现 KIGG 数据库的默认情况是用latin1编码的(DEF ...

  6. Introduction to the Build Lifecycle

    Introduction to the Build Lifecycle Table Of Contents Build Lifecycle Basics Setting Up Your Project ...

  7. UVALive 6931 Can't stop playing (Regionals 2014 >> Europe - Central)

    题目 一开始有一个双头队列,每次添加一个数(这是数是二的幂,所有数的和不大于\(2^13\)),由你来决定添加到队头还是队尾.如果队列里面相邻的两个数相同,设它们都是\(x\),那么这两个数会合并为\ ...

  8. Laravel创建Model

    它已被用于CI框架.最近学习使用Laravel框架,要总结一些遇到的问题是一个创纪录,供以后调用.此外,我希望能够碰到同样的问题的朋友的帮助. 在Laravel数据库表是根据Laravel写好的程序去 ...

  9. Android中连接蓝牙设备时遇到createRfcommSocketToServiceRecord的UUID问题和BluetoothSocket的connect失败

    [问题] 折腾: [记录]编写Android中的蓝牙模块驱动和底层HART设备 期间,参考: Bluetooth | Android Developers – ManagingAConnection ...

  10. anroid里面的post请求

    一.需要用到的场景 在jQuery中使用$.post()就可以方便的发起一个post请求,在android程序中有时也要从服务器获取一些数据,就也必须得使用post请求了. 二.需要用到的主要类 在a ...