Sometimes, design can be at odds with usability.

Consider our GAA App. We had a very specific thing we wanted to do by design.

There's various buttons on screen during a game; Buttons to start/stop the play, buttons to alter the time line, and buttons to record points or goals. There's even more, but you get the idea.

One thing we wanted to avoid was "pocket presses", specifically around when a game starts or stops. Usually, the user sets up the game, and is ready to press the start button when the ball is thrown in; we wanted to avoid when they set up the game, pocket their device, and accidentally hit the start button.

So, we elected to make the start/stop button a "Long Press". Essentially, tapping on it doesn't really do anything, you have to tap, and hold.

We knew discoverability would be a problem, so we added a graphic hint. If you tap and hold the button, it will animate filling a circle in around the button. When the circle finishes the button is "pressed". If you let go, the circle no longer fills. It "kinda" works

You can take a look at how it operates below:

But still, every now and again, someone would come to me and say "Hey, I downloaded your app, but I couldn't get it working". Each time, I knew what the problem was, told them about it and they were happy. But, I've no idea about people who may have struggled, and simply deleted the app.

Now, iOS 17 offers an in-built way of doing Tips. Here's how it now looks:

Nice! Adding it was super easy.

Create a struct to hold the tip. This struct inherits from TipKit's Tip object:

struct LongPressTip: Tip {
  var title: Text {
    Text("Tap and Hold to get started")
      .foregroundStyle(Color(.tintColor))
  }
  var message: Text? {
    Text("To avoid pocket-mistakes, this button requires tap and hold.  Tap and hold it for 1.5 seconds for throw-in")
  }
}

In the App's init() call, configure TipKit:

try? Tips.configure([
  .displayFrequency(.immediate),
  .datastoreLocation(.applicationDefault)])

This sets up our tips to display immediately, and store if it has been previously stored in the apps default location.

Within the view where I wanted to show the tip, I added it as a private variable. This is to allow it to be referenced for both display, and to mark it as "seen":

private let longPressTip = LongPressTip()

At the control I wished to show the tip, I added a popoverTip:

Image(systemName: nextStateTitle().1)
  .font(Font.system(size: 45, weight: .light))
  .overlay(ProgressCircle(progress: $progress).scaleEffect(1.1))
  .padding(.top, 5.0)
  .popoverTip(longPressTip)

Finally, when the long press gesture is actually used, we mark the tip as no longer being needed, as the user has discovered the tip":

.onLongPressGesture(minimumDuration: 1.5, maximumDistance: 50, perform: {
  // What to do when it completes - well, that's the button being pressed
  longPressTip.invalidate(reason: .actionPerformed)
  nextGameState()

It's as simple as that.


Thanks for reading the Tapadoo blog. We've been building iOS and Android Apps since 2009. If your business needs an App, or you want advice on anything mobile, please get in touch