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).
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
Before I started writing code, I made a list of goals and requirements:
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
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:
// 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)) bubblePath.close()
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.
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)
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
@IBInspectable functionality and a few lines of Swift code, even designers can deliver custom iOS controls.