Creating an iOS Chat Bubble with Tails in Swift — the easy way

Virtually everyone who’s used an iOS device has used the iMessage application to send and receive text messages to other iOS users or non-iOS users via SMS. This tutorial will teach you how to create the familiar chat bubble with tail UI element used in the built-in Apple Message application.

This tutorial was created using Swift 4 and Xcode 9.1. Most of the concepts covered apply to previous versions of Swift and Xcode also.

Virtually everyone who’s used an iOS device has used the iMessage application to send and receive text messages to other iOS users or non-iOS users via SMS. This tutorial will teach you how to create the familiar chat bubble with tail UI element used in the built-in Apple Message application.

Chat bubbles in action — in Apple Messages

The objective of this tutorial article isn’t to instruct how to build a fully-functional chat application. I’m going to focus specificlly on how to easily create the dynamically sizing bubble.

Design Requirements

While the chat bubble with tail is familiar, it presents some challenges for development:

  1. The horizontal and vertical size of the bubbles must be expandable. The size of a bubble containing a single word will be much smaller than one containing an entire paragraph (or, for example, an image).
  2. The middle of the chat bubble should stretch to fit its content — but the four corners of the chat bubble should not be stretched, and must remain exactly as designed.
  3. The tail should point to opposite sides of the chat window to indicate whether the message has been sent or received.
  4. The color of the bubble should match app branding, and may suggest a visual cue — for example, in Apple Message, blue indicates messages sent to other iOS/macOS users, green indicates messages sent to conventional SMS users (e.g. Android users) and gray indicates received messages.

Implementation Approach

As in most development topics, there’s more than one way to implement chat bubbles. Some implemeters draw bubbles manually using bezier curves, but using stretchable images is much simpler, and more common.

In this tutorial I’ll cover what I think is the simplest approach, and the basis for how bubbles with tails should probably implemented in most applications:

  1. The bubble itself is based on a simple bitmap (I created mine as a vector using Sketch, and then exported them to scaled png files)
  2. The dynamic bubble size is accomplished using the standard UIImage resizableImage:withCapInsets:resizingMode method provided by Cocoa Touch.
  3. The chat bubble can be set to any color in code using the UIImageView tintColor property — so your app can use any color it needs, and indicate different types of messages with color just as Apple Messsages does.

OK, let’s walk through the implementation. A link to the source code can be found at the end of this article.

Creating the Resizable Images

First create two images: one with the tail on the left, the other with the tail on the right. The color you use doesn’t really matter, since you’ll use the UIImageView.tintColor to color the bubble at runtime later.

You can create images to use in this technique using whatever tools your most proficient with. I used Sketch, but you can use Photoshop or any other application that saves raster bitmaps. When finished, save the final images as png files.

The first key to this technique is taking note of how many points (pixels in the 1x image) should remain fixed at each of the four corners of the bubble image(s). In my image, I’ve highlighted the four corners: each is 21 pixels wide and 17 pixels high.

Chat bubble image with right tail

These fixed corners will have the following effect in the final application:

  1. The minimum bubble should be 42 x 32 points — so that these perfect corners are never distorted by stretching or compressing. This is fine for most applications, but if you need larger or smaller corners, just design the image at whatever size meets your needs.
  2. When the bubble needs to grow, the empty middle space (between the corners) will be stretched to match the necessary size.

The blue bubble I designed is the one used whent he user sends a message. I could have made it any color, but for design purposes it’s blue.

I also designed a gray bubble with the tail on the left for received messages (actually, in Sketch, I just copied it and used the mirror button). Since the “recevied message” bubble is a mirror of the “send” bubble, the corrners will be identical. Again, the color doesn’t matter so long as it’s not white.

Chat bubble image with left tail

Add Images to the Xcode Project

Next, add the bubble images to your Xcode project. I exported 1x, 2x and 3x images from Sketch to png files, and created an Image asset for each type of bubble in Xcode. My sent bubble is named chat_bubble_sent, while the received bubble is called chat_bubble_received.

Bubble Image Assets in Xcode

Create a User Interface

Most applications that use chat bubbles are messaging applications. Chat apps can become quite complex to implement and understand, and would make this tutorial a much longer read! To keep things simple, I’m just going to focus on the bubble itself. The following user interface is designed to demonstrate the technique of creating, resizing and coloring the chat bubble.

Chat Bubble Tutorial User Interface
  1. First is a slider control that allows the bubble height to be changed, so we can move the slider and observe the bubble at any height from 34 to 400.
  2. Next are Two buttons. These switch the display between the sent (right-tailed) bubble and the received (left-tailed) bubble.
  3. Several color options to choose from to change the color of the bubble dynamically.
  4. A UIImageView where the bubble will be displayed. In a chat or message application, this UIImageView will typically be part of the content of a UITableViewCell or UICollectionViewCell.

Setting the Bubble Image

In this tutorial application, the bubble image itself is assigned to the UIImageView’s .image property when either the Sent or Received (2 in the diagram above) buttons are tapped.

Each button calls the following helper function changeImage, passing the name of the image to use.

1: func changeImage(_ name: String) {
2:    guard let image = UIImage(named: name) else { return }
3:    bubbleImageView.image = image
4:       .resizableImage(withCapInsets:
5:                          UIEdgeInsetsMake(17, 21, 17, 21),
6:                          resizingMode: .stretch)
7:       .withRenderingMode(.alwaysTemplate)
8: }

changeImage does does the following:

  • Line #2 loads the named asset from Assets.xassets. When the Sent button is tapped, the asset name chat_bubble_sent is passed; when the Received button is tapped, the chat_bubble_received asset name is passed.
  • Line #4 calls the UIImage method resizableImage:withCapInsets:resizingMode to create a version of the image that can be stretched — except for the four corner regions (each 21 x 17) that we noted when we designed them.
  • Line #7 Calls the UIImage method .withRenderingMode(.alwaysTemplate) to create a version of the resizable image that ignores color information. This modification to the image allows us to use the tintColor to make the bubble any color we need it to be at runtime.

Changing the Bubble Color

Changing the bubble color is quite simple. Since we used the .withRenderingMode(.alwaysTemplate) method when assigning the image to the UIImageView, we can use the .tintColor property to set the color of the image’s primary color.

In this case, I just grabbed the .backgroundColor property of the button the user taps in line #2 to set the color.

1: @IBAction func colorButtonTapped(_ sender: UIButton) {
2:    bubbleImageView.tintColor = sender.backgroundColor
3: }

Changing the Bubble Size

For the tutorial appliation, the height of the bubble is changed by moving the UISlider control. The control is set so that the bubble must be at least 34 points (2 x 17). This ensures that the corners will never be compressed vertically beyond the minimum defined at design time.

1: @IBAction func sliderChanged(_ sender: UISlider) {
2:    bubbleHeight.text = “(sender.value)”
3:    bubbleHeightConstraint.constant = CGFloat(sender.value)
4: }

Line 3 simply changes the constant value of the UIImageView height constraint.

Run the Tutorial App

Summary

I hope this helps you understand how to approach this type of requirement when using Swift for iOS applications. While this isn’t the only way to approach the impelmentation, this is a simple and effective method. You can see in the code below that we only used 40 lines of code to implement the entire solution!

Take this technique further by incorporating bubbles in your own app. The basis for this approach is a resizable UIIMageView, with a resizable UIImage with capInsets. Anywhere in your solution where you can add a UIImageView to a UIView container, you can leverage this technique.

Full Swift Source Code

Following is the full source code for the tutorial.

01: class ViewController: UIViewController {
02:
03: @IBOutlet weak var slider: UISlider!
04: @IBOutlet weak var bubbleImageView: UIImageView!
05:
06: @IBOutlet weak var bubbleHeight: UILabel!
07: @IBOutlet weak var bubbleHeightConstraint: NSLayoutConstraint!
08:
09: override func viewDidLoad() {
10:    super.viewDidLoad()
11: }
12:
13: @IBAction func sliderChanged(_ sender: UISlider) {
14:    bubbleHeight.text = “(sender.value)”
15:    bubbleHeightConstraint.constant = CGFloat(sender.value)
16: }
17:
18: @IBAction func sentButtonTapped(_ sender: UIButton) {
19:    changeImage(“chat_bubble_sent”)
20:    bubbleImageView.tintColor = UIColor(named:             
                                   “chat_bubble_color_sent”)
21: }
22:
23: @IBAction func receivedButtonTapped(_ sender: UIButton) {
24:    changeImage(“chat_bubble_received”)
25:    bubbleImageView.tintColor = UIColor(named: 
                                   “chat_bubble_color_received”)
26: }
27:
28: func changeImage(_ name: String) {
29:    guard let image = UIImage(named: name) else { return }
30:    bubbleImageView.image = image
31:           .resizableImage(withCapInsets:
32:                           UIEdgeInsetsMake(17, 30, 17, 30),
33:                           resizingMode: .stretch)
34:           .withRenderingMode(.alwaysTemplate)
35: }
36:
37: @IBAction func colorButtonTapped(_ sender: UIButton) {
38:    bubbleImageView.tintColor = sender.backgroundColor
39: }
40: }

Download the Code

You can download the code for this tutorial here: https://github.com/robkerr/TutorialChatBubble

Leave a Reply

Your email address will not be published. Required fields are marked *