在实现qq聊天界面的过程中,使用UITableViewCell碰到了不少问题,这里还是记录一下以免遗忘。
气泡聊天cell的实现,网上最多的方法还是:
1.手动计算设置frame的值,文本的size使用boundingRectWithSize函数动态计算
2.气泡的实现,拉伸使用resizableImageWithCapInsets函数,还需要设置文本UILabel的frame在气泡之内
因为后来发现tableView在绘制cell时是先询问高度,再询问cell,可是询问高度时cell还未计算,如果先计算一遍高度的话效率上又不划算,
这种方案多少还是不完善,最后也未采用,所以直接贴出代码作为参考。
class ChatTableViewCell: UITableViewCell { @IBOutlet weak var bubbleImage: UIImageView!
@IBOutlet weak var time: UILabel!
@IBOutlet weak var icon: UIImageView!
@IBOutlet weak var content: UILabel! var height:CGFloat! class func initChatCell(tableView:UITableView,message:TextMessage,iconname:String) -> ChatTableViewCell?{ var t:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("ChatTableViewCell") if t == nil{
t = NSBundle.mainBundle().loadNibNamed("ChatTableViewCell", owner: nil, options: nil).first as? UITableViewCell
}
let cell:ChatTableViewCell = t as! ChatTableViewCell // 屏幕的宽度
let screenW = UIScreen.mainScreen().bounds.width
let paddingSize:CGFloat = 5
let timeHided = false if timeHided{
cell.time.frame = CGRectMake(0,0, 0, 0)
cell.time.hidden = true
}else{
cell.time.textAlignment = NSTextAlignment.Center
cell.time.textColor = UIColor.darkGrayColor()
cell.time.font = UIFont.systemFontOfSize(13)
cell.time.text = message.time
cell.time.frame = CGRectMake(0,paddingSize,screenW,20)
}
cell.time.layoutIfNeeded() let timeH = cell.time.frame.maxY + paddingSize
let iconSize:CGFloat = 40
let bubble_insets = (message.role == Role.Me ? UIEdgeInsetsMake(15,20,15,23):UIEdgeInsetsMake(15,23,15,20))
let iconExtendWith = iconSize+paddingSize let textString = NSString(string: message.text ?? "")
let size = CGSizeMake(screenW-iconExtendWith*2-bubble_insets.left-bubble_insets.right, CGFloat(MAXFLOAT))
let attributes = [NSFontAttributeName: UIFont.systemFontOfSize(15)]
let textSize = textString.boundingRectWithSize(size, options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: attributes, context: nil)
let bubbleWidth = textSize.width+bubble_insets.left+bubble_insets.right
let bubbleHeight = textSize.height+bubble_insets.top+bubble_insets.bottom
var iconF:CGRect
var bubbleF:CGRect
var contentF:CGRect
var bgImage:UIImage
if message.role == Role.Me{
iconF = CGRectMake(screenW-paddingSize-iconSize, timeH+bubble_insets.top*0.7,iconSize, iconSize) bubbleF = CGRectMake(iconF.origin.x-bubbleWidth-paddingSize,timeH,bubbleWidth,bubbleHeight)
bgImage = UIImage(named:"chat_send_nor@2x")!
}else{
iconF = CGRectMake(paddingSize, timeH+bubble_insets.top*0.7,iconSize, iconSize) bubbleF = CGRectMake(iconF.maxX+paddingSize,timeH,bubbleWidth,bubbleHeight)
bgImage = UIImage(named:"chat_recive_nor@2x")!
}
contentF = CGRectMake(bubbleF.origin.x+bubble_insets.left, bubbleF.origin.y+bubble_insets.top, textSize.width, textSize.height) cell.icon.image = UIImage(named:iconname)
cell.icon.frame = iconF
cell.icon.layoutIfNeeded() cell.content.font = UIFont.systemFontOfSize(15)
cell.content.numberOfLines = 0
cell.content.lineBreakMode = .ByWordWrapping
cell.content.text = message.text
cell.content.frame = contentF
cell.content.layoutIfNeeded() let H = floor(bgImage.size.height*0.5)
let W = floor(bgImage.size.width*0.5)
let insets = UIEdgeInsetsMake(H, W, bgImage.size.height-H-1, bgImage.size.width-W-1)
cell.bubbleImage.image = bgImage.resizableImageWithCapInsets(insets)
cell.bubbleImage.frame = bubbleF
cell.bubbleImage.layoutIfNeeded() cell.layoutIfNeeded()//需要在xib中禁用autolayout,layoutIfNeeded否则失效,cell第一次会按xib中的布局显示
cell.height = max(cell.bubbleImage.frame.maxY,cell.icon.frame.maxY) return t as? ChatTableViewCell
} override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.backgroundColor = UIColor.whiteColor()
} override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated) // Configure the view for the selected state
}
// override func layoutSubviews() {
// super.layoutSubviews()
// }
}
cell是在xib中实现的,需要注意的是:
1.需要先拖bubbleImage,再拖uilabel,因为文字要显示在气泡之上。否则的话文字会被气泡覆盖掉
2.需要在 xib中禁用autolayout,否则好像手动设置的frame,调用layoutIfNeeded不起作用。而约束又未设置,cell就会以初始布局显示。
附上目前使用的三种类型的消息的定义:
//
// Message.swift
// OrayTalk
//
// Created by 梅利健 on 16/6/4.
// Copyright © 2016年 meilijian. All rights reserved.
// import Foundation public enum Role:Int {
case Me
case Other
} class BaseMessage:NSObject{
var time:String!
var text:String!
override init() {
super.init()
} init(time:String,text:String) {
self.time = time
self.text = text
}
} class TextMessage:BaseMessage{ var role:Role = Role.Me class func messageWithDic(dic:[String:AnyObject],role:Role) -> TextMessage{ let message = TextMessage() message.setValuesForKeysWithDictionary(dic)
message.role = role
return message }
override init() {
super.init()
}
init(time:String,text:String,role:Role) {
super.init(time: time,text: text)
self.role = role
}
} class TipMessage: BaseMessage { } class ProgressMessage:BaseMessage{
var role:Role = Role.Me
var total: UInt64 = 0
var transfered: UInt64 = 0
var lastdate:NSDate = NSDate()
var lasttransfered: UInt64 = 0
var speed:Double = 0
init(time:String,text:String,role:Role) {
super.init(time: time,text: text)
self.role = role
} var SpeedDescription:String{
get{
if speed >= 1024*1024{
return NSString(format: "%0.2fM/s", speed/(1024*1024)) as String
}
else if speed >= 1024{
return NSString(format: "%0.2fK/s", speed/(1024)) as String
}
else if speed >= 0{
return NSString(format: "%0.2fB/s", speed) as String
}
else{
return NSString(format: "%0.2fB/s", 0) as String
}
}
}
}