How to add a tap gesture to UISlider’s thumb in UIKit — Telegram-Drawing-Text-Editing, ep. 5

Ivan Pryhara
5 min readJan 22, 2023

--

Why I really needed to add tap gesture?

In this article you’ll learn how to add a tap gesture to a UISlider’s thumb and make it useful. For example to achieve this interesting behavior:

Maybe the image is not enough to understand what kind of behavior I’m trying to show you, so here a quick explanation. The initial state of UISlider is represented above “inactive” label. Black line is a left side of iPhone screen, literally a border. So we kind of hide a half of our slider to get a bit more active space and show that slider option is available but it is not the main tool. However, when user decides to interact with a slider we move it toward screen center by some value(depends on a size of the slider) to make it visible. After we leave a thumb slider move back to its initial position.

Well, for this feature I had to think that I need to add tap gesture, because it seemed logical to me.

How to setup tap gesture for a thumb in UISlider?

My first I idea was to add a simple touch gesture recognizer to my custom UISlider class( implementation and detailed tutorial about the custom slider you can find by tapping on this link) but this only works when I tap on a slider itself, I mean a base view, which obviously is not expected behavior. My next attempt lead me to try to add the same recognizer to a thumb of UISlider class, but it didn’t work either. So I decided to dig a bit deeper and found two methods beginTracking(_, with:) and endTracking(_, with:) in UIControl, the class that is a base class of UIView, which a base class of UISlider

// Inside your UISlider class

override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
super.beginTracking(touch, with: event)
// Your code goes here

return true
}

override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
// Your code goes here
}

// ...

That’s it, this is all you need to make sure that your code will be executed whenever you dragging thumb of your UISlider

How to get the behavior of hiding/showing UISlider depends on interaction state?

Preparations

This part describes how to implement behavior that was showed at the begin of this article.

Let’s start with implementing IPSliderDelegate which looks as though:

protocol IPSliderDelegate {
func touchesBegan() -> Void
func touchesEnded() -> Void
}

The next step is to add a delegate property to IPSliderDelegate implementation, in addition to call delegate methods on beginTracking and endTracking — overriden methods from UIControl class:

/// somewhere in IPSliderView 

var delegate: IPSliderDelegate?

override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
super.beginTracking(touch, with: event)
delegate?.touchesBegan() // Add a method here

return true
}

override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
delegate?.touchesEnded() // and here
}

Setting up a class that holds IPSliderView and becomes its delegate

In the app the slider is visible only when a keyboard is on the screen, i.e. text editing is active. So by when you are not editing any text, the slider is hidden behind left side of the iPhone’s screen. Here’s how I define IPSliderView on main view controller:

let fontSizeSlider: IPSliderView = {

let dimension = widthToDimensions(35)

let slider = IPSliderView(frame: CGRect(x: 0, y: 0,
width: dimension.width,
height: dimension.height))

slider.minimumValue = 30
slider.value = 35
slider.maximumValue = 70
// Implementation of this method won't be covered in this article
slider.addTarget(nil, action: #selector(fontSizeSliderValueChanged(_:)), for: .valueChanged)
slider.accessibilityIdentifier = "fontSizeSlider"

return slider

}()

The next part is vital, you need to put this code to viewDidLoad as is or as a part of some setting up method, because we do a few transformations on slider:

  1. As you can see I’m setting negative maxY of a frame on X. This is why, because we do rotate our view, but we haven’t(and I believe we can’t) changed AXES, so after rotation slider’s frame’s X axis is responsible for vertical position, meanwhile Y for horizontal. And this is why I’m using maxY for X axis. We could use any magic big number, but it cause a bad behavior when you want to animate UISlider appearance. Meanwhile for Y axis I’m using fourthOfTheScreenFromAbove which is self-explanatory. But you shouldn’t define this property inside methods scope, I would define this property as static somewhere you think is the best place for it, because you’ll use it throughout the app in the future. Or if you want the slider initially be a half hidden you can substitute an X value to 0, and this will work, because of next. By default origin point of element in UIKit is top left corner, so when we rotate our view the way its right side is on top now our initial point is set to bottom left corner. And when you set center point of (x:0, y: someValue) it means this view should move itself toward to left until its center met conditions we’ve setup.
  2. Transform the slider to rotate it by -90 degrees
  3. And setup a delegate as a self
let fourthsOfTheScreenFromAbove = UIScreen.main.bounds.maxY - UIScreen.main.bounds.maxY * 0.6

// 1
fontSizeSlider.center = CGPoint(x: -fontSizeSlider.frame.maxY /* or 0 */, y: fourthsOfTheScreenFromAbove)
// 2
fontSizeSlider.transform = CGAffineTransform(rotationAngle: (.pi * 3) / 2)
// 3
fontSizeSlider.delegate = self

Conforming to IPSliderDelegate protocol

extension YourController: IPSliderDelegate {
func touchesEnded() {
UIView.animate(withDuration: 0.3) {
self.fontSizeSlider.center = CGPoint(x: 0, y: fourthsOfTheScreenFromAbove)
}
}

func touchesBegan() {
UIView.animate(withDuration: 0.3) {
self.fontSizeSlider.center = CGPoint(x: self.fontSizeSlider.sFrame.maxY * 0.8, y: fourthsOfTheScreenFromAbove)
}
}
}

When touches began we put center X axis to 80 percent of frame’s maxY. If you finish interacting with the slider we just set its center to initial value. If you want to observe how I implemented the way slider appear with keyboard hiding and appearing you could check this out on my Github. Or if you want me to write an article how to wired everything up with detailed explanation, please let me know in the comments bellow.

--

--

Ivan Pryhara
Ivan Pryhara

No responses yet