聊天界面之气泡文本cell(一)
在实现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 } } } }