How to train user interactions with a custom view.

Alex Mathers
hiTech by Nudge Rewards
4 min readJan 8, 2018

Not all user interactions are created equal. Some are obvious and intuitive, and others are… less than that. While it would be great if you could create a design where users knew exactly what to do the moment they lay eyes on your app, more often than not, they will need a little coaching for new behaviors.

So how do you coach them? Well, here’s one idea: a small, animated label that will prompt the user to perform the desired action.

The design, showing a a user prompt with ‘Tap to Expand’.
Here’s a Gif of the behavior fully implemented. We should probably only show the prompt when the user first visits the page, and not after they’ve completed the desired action. But that’s not how Gifs work.

Consider the design above; We want to present a bulleted list beneath a photo, where each bullet contains a string of variable length. In order to see the full string, we want users to tap on the bulleted item so that it will expand. The first steps are to use color and type to suggest an action. We’ve given the text a link-ish color, a heavier font weight, and the ellipses help show that this content is being truncated — but that doesn’t feel like enough on its own. In order to be sure the interaction isn’t missed by users wouldn’t it be great to add a custom prompt that says TAP TO EXPAND like the one above?

It looks great…but also kind of complicated to build. It’s a custom shape, it’s got gradients, and we’ll want part of it to expand to fit variable text. Yikes.

Well, it isn’t as bad to build as it seems. Here’s how I did it.

First, we need to decide how we’re going to create the background for the label. It might seem easiest to just export that asset at first, and place the label on top, but there are a few things to consider before that. Will the label always be the same length? What about with localization? What if we want to change the arrow position, or scale the bubble without the arrow scaling as well? After considering those requirements, we’ll want to create the view programmatically with some custom drawing code.

Since I don’t do this often I turned to a helpful tool called PaintCode. This is an app that will let you design UI elements the way you would in Sketch or Illustrator, and spit out custom drawing code in Swift or Obj-C.

The shapes created in PaintCode. One rectangle with rounded edges, and one triangle, flipped vertically.

Above is the result of the very simple drawing I did in PaintCode. It’s simply a large rectangle with rounded corners, and a smaller triangle below. Both have the same gradient applied. There’s a small inconsistency in the gradients but we’ll fix that later in the code that was generated for us below.

The code generated for us from the drawing in PaintCode.

Pretty easy right? Well, we’re not done quite yet. PaintCode gives us a great start, but to make this view a little more versatile we want to do a few things. First — we want to replace all the hard coded values with relative ones so that the bubble can change size comfortably. Second — we want to allow the arrow to appear on the left, right, or in the center. And third we want to add a label, and have the background drawing expand to fit that label’s text.

So let’s start with the first one: relative values. The idea here is that instead of using (somewhat) arbitrary values for the various CGPoints, we’ll use values that are relative to the size of the view.

The adjusted drawing code, using relative values.

We start by adding some constants at the top, to make the drawing code a little more readable. This code exists inside the view’s draw(_rect: CGRect) method, so let width = rect.width refers to the view’s frame. I won’t go through all of the code but you can see that on line 18 instead of having a static size of 100x24 the rectangle will now use relative values to be equal widths to the view, and 3/4 of the height.

Next, on line 29 we add some code to make the arrow relative to a new property on the view arrowPosition which is an enum with 3 cases: .left , .right , and .center .

Now that we have the drawing done. It’s time to add the label, and some helper methods to initialize and resize the view to fit the label’s text.

We add a custom initializer init(text: String, arrowPosition: ArrowPosition = .center) where we set up the label. Note that we provide a default value of .center for extra convenience. Also inside the initializer, we call a helper method sizeThatWillFit(text: String) -> CGSize to determine the size of the view. There are also a couple new constants suggestedHeight and padding that will determine the height of the view, and how much padding is added to either side of the text label.

Now you can simply create an instance of the view like so:
let view = UserPromptView(text: "TAP TO EXPAND")

Or if you don’t want to use the default arrow position (center):
let view = UserPromptView(text: "TAP TO EXPAND", arrowPosition: .left)

And there we have it! A nice custom view ready to animate.

--

--