July 5, 2017

Xcode as a Design Tool: the Customizable Tooltips Case

  • swift
  • uidesign
  • xcode

The other day I was reviewing the implementation of an iOS app design I helped design. While most of the design was looking great, one thing immediately struck my eye. The tooltips I carefully crafted didn’t look at all like how I intended them to look. When I asked the developers about it, they said it would be too difficult to implement them without using an existing framework (and its restricted styling options).

Design vs Implementation

That made me wonder if I could find an easier way to sneak the right tooltips in. I started diving a bit deeper into drawing and styling custom shapes in Swift/Xcode. Since the tooltips in my design don’t need complex animations or interactions, it was not my intent to write a custom tooltip framework (since they already exist). What I did want to explore was how a designer with a base knowledge of programming could contribute to a better handoff of custom styled controls.

What if I could make it as easy for the developer to implement my tooltip design as to drag a button on a storyboard

After digging through some useful parts of Apple’s iOS development documentation, I decided to address my challenge step by step.

Step 1: Goals

Before I started writing code, I made a list of goals and requirements:

  • The tooltip should look exactly the way I want it to look (including the tilted tip, multiple shadows and padding).
  • It should be scalable and should play nice with Auto Layout.
  • It should be able to contain whatever objects you want it to contain, not just text.
  • Adding a tooltip should be as easy as dragging and dropping an object.
  • Changing the style of the tooltip should be a matter of tweaking a few variables and should not require much knowledge of the design logic itself.

Step 2: The Inspectables


Turns out, you can add the @IBInspectable annotation to make parameters adjustable from the inspector. That's even better than I expected.After creating a new Cocoa class in Xcode (File > New > File…), naming it TooltipView.swift and making it a subclass of UIView, I added some variables to make things customizable.

@IBInspectable var arrowTopLeft: Bool = false
@IBInspectable var arrowTopCenter: Bool = true
@IBInspectable var arrowTopRight: Bool = false
@IBInspectable var arrowBottomLeft: Bool = false
@IBInspectable var arrowBottomCenter: Bool = false
@IBInspectable var arrowBottomRight: Bool = false
@IBInspectable var fillColor: UIColor = UIColor.white
@IBInspectable var borderColor: UIColor = UIColor(red:0, green:0, blue:0, alpha:0.05)
@IBInspectable var borderRadius: CGFloat = 18
@IBInspectable var borderWidth: CGFloat = 1
@IBInspectable var shadowColor: UIColor = UIColor(red:0, green:0, blue:0, alpha:0.14)
@IBInspectable var shadowOffsetX: CGFloat = 0
@IBInspectable var shadowOffsetY: CGFloat = 2
@IBInspectable var shadowBlur: CGFloat = 10

Step 3: Here’s Where the Math Comes in

To translate the tooltip shape to code, I first mapped it on an X and Y axis. Based on three variables, I was able to define all the necessary coordinates: tooltipWidth, tooltipHeight and borderRadius.


// Define Bubble ShapeTranslating this shape to Swift code wasn’t that hard, the result is even pretty readable:

let bubblePath = UIBezierPath()
// Top left corner
bubblePath.move(to: topLeft(0, borderRadius))
bubblePath.addCurve(to: topLeft(borderRadius, 0), controlPoint1: topLeft(0, borderRadius / 2), controlPoint2: topLeft(borderRadius / 2, 0))
// Top right corner
bubblePath.addLine(to: topRight(borderRadius, 0))
bubblePath.addCurve(to: topRight(0, borderRadius), controlPoint1: topRight(borderRadius / 2, 0), controlPoint2: topRight(0, borderRadius / 2))
// Bottom right corner
bubblePath.addLine(to: bottomRight(0, borderRadius))
bubblePath.addCurve(to: bottomRight(borderRadius, 0), controlPoint1: bottomRight(0, borderRadius / 2), controlPoint2: bottomRight(borderRadius / 2, 0))
// Bottom left corner
bubblePath.addLine(to: bottomLeft(borderRadius, 0))
bubblePath.addCurve(to: bottomLeft(0, borderRadius), controlPoint1: bottomLeft(borderRadius / 2, 0), controlPoint2: bottomLeft(0, borderRadius / 2))

In the same way, I added the different tooltip arrows to the path. I now had a UIBezierPath object describing the tooltip shape, but my storyboard was still empty.

Step 4: Let’s Paint

The last thing I needed to do before switching to the storyboard, was making sure the path was painted. UIKit lets you stack as many layers on top of each other as you like while reusing the same shape definition. I split the tooltip into three layers (shadow, border and fill) and inserted them into the custom view:

// Shadow Layer
let shadowShape = CAShapeLayer()
shadowShape.path = bubblePath.cgPath
shadowShape.fillColor = fillColor.cgColor
shadowShape.shadowColor = shadowColor.cgColor
shadowShape.shadowOffset = CGSize(width: CGFloat(shadowOffsetX), height: CGFloat(shadowOffsetY))
shadowShape.shadowRadius = CGFloat(shadowBlur)
shadowShape.shadowOpacity = 0.8
// Border Layer
let borderShape = CAShapeLayer()
borderShape.path = bubblePath.cgPath
borderShape.fillColor = fillColor.cgColor
borderShape.strokeColor = borderColor.cgColor
borderShape.lineWidth = CGFloat(borderWidth*2)
// Fill Layer
let fillShape = CAShapeLayer()
fillShape.path = bubblePath.cgPath
fillShape.fillColor = fillColor.cgColor
// Add Sublayers
self.layer.insertSublayer(shadowShape, at: 0)
self.layer.insertSublayer(borderShape, at: 0)
self.layer.insertSublayer(fillShape, at: 0)

Step 5: The Proof of the Pudding

Time to put this brand new custom control to the test.


To my own surprise, I saw exactly the result I had hoped for: a responsive, easy-to-use, framework-free and customizable tooltip. Long story short: Xcode and Swift are remarkably easy when it comes to designing custom iOS controls. They might even spare you (and your developers) a headache.


By mixing and matching some basic math, Xcode’s @IBDesignable and@IBInspectable functionality and a few lines of Swift code, even designers can deliver custom iOS controls.

You can find the full result on Github.