Calculation of the frame size of the Chat Bubble(iOS Client)
Starting with the implementation, we will need a UICollectionViewController
.
import UIKit
class ChatViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
}
}
Then, we setup the background of the view and configure the collectionView
and call these methods inside inside viewDidLoad
.
// Setup View
func setupView() {
self.view.backgroundColor = UIColor.rgb(red: 236, green: 229, blue: 221)
}
// Setup Collection View
func setupCollectionView() {
collectionView?.backgroundColor = .clear
collectionView?.delegate = self
collectionView?.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height - 50)
// register cell
collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
The -50
is supposed to be occupied by the Message Container
.
Continuing with this, we implement the CollectionView
methods as follows:
// Number of items to be displayed
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
// Configure Cell
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = .red
return cell
}
When trying to run this, we get the following output.
We need to increase the width of each cell as well give it a height for displaying a message.
// Calculate Bubble Height
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 100)
}
To remove the cell spacing we override the edge insets as:
// Calculate Bubble Height
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 100)
}
We configure a custom Chat Cell to customize the cell according to the needs:
class BaseCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {}
}
class ChatLogMessageCell: BaseCell {
let messageTextView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFontOfSize(18)
textView.text = "Sample message"
textView.backgroundColor = UIColor.clearColor()
return textView
}()
let textBubbleView: UIView = {
let view = UIView()
view.layer.cornerRadius = 15
view.layer.masksToBounds = true
return view
}()
static let grayBubbleImage = UIImage(named: "bubble_gray")!.resizableImageWithCapInsets(UIEdgeInsetsMake(22, 26, 22, 26)).imageWithRenderingMode(.AlwaysTemplate)
static let blueBubbleImage = UIImage(named: "bubble_blue")!.resizableImageWithCapInsets(UIEdgeInsetsMake(22, 26, 22, 26)).imageWithRenderingMode(.AlwaysTemplate)
let bubbleImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = ChatLogMessageCell.grayBubbleImage
imageView.tintColor = UIColor(white: 0.90, alpha: 1)
return imageView
}()
override func setupViews() {
super.setupViews()
addSubview(textBubbleView)
addSubview(messageTextView)
textBubbleView.addSubview(bubbleImageView)
textBubbleView.addConstraintsWithFormat("H:|[v0]|", views: bubbleImageView)
textBubbleView.addConstraintsWithFormat("V:|[v0]|", views: bubbleImageView)
}
}
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
Using this custom cell, we can now add text to the message cells and the only thing left is to configure the frame size of each chat bubble. To implement this, we will need an array string of 4
messages which can be used to test anc calculate dynamic frame for each message.
let messages: [String] = ["Hello Susi", "I love FossAsia", "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s", "The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from de Finibus Bonorum et Malorum"]
We now modify the sizeForItemAt
method to calculate the frame size.
// Calculate Bubble Height
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let message = messages[indexPath.row]
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: message).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
return CGSize(width: view.frame.width, height: estimatedFrame.height + 20)
}
And accordingly modify the cellForItemAt
method as well.
// Configure Cell
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let message = messages[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ChatLogMessageCell
cell.messageTextView.textColor = .black
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: message).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
if indexPath.row % 2 == 0 {
cell.bubbleImageView.image = ChatLogMessageCell.grayBubbleImage
cell.bubbleImageView.tintColor = .white
cell.messageTextView.textColor = UIColor.black
cell.messageTextView.frame = CGRect(x: 16, y: 0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 30)
cell.textBubbleView.frame = CGRect(x: 4, y: -4, width: estimatedFrame.width + 16 + 8 + 16, height: estimatedFrame.height + 20 + 6)
} else {
cell.messageTextView.frame = CGRect(x: view.frame.width - estimatedFrame.width - 16 - 16, y: 0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:view.frame.width - estimatedFrame.width - 16 - 8 - 16, y: -4, width: estimatedFrame.width + 16 + 8 + 10, height: estimatedFrame.height + 20 + 6)
cell.bubbleImageView.image = ChatLogMessageCell.blueBubbleImage
cell.bubbleImageView.tintColor = UIColor.rgb(red: 220, green: 248, blue: 198)
}
cell.messageTextView.text = message
return cell
}
Below is the final output: