Reputation: 12847
I created a Slider (operating as control of the video, like YouTube has at the bottom) and set the maximum (duration) and minimum values. And then used SeekToTime
to change the currentTime. Now, the user can slide the thumb of the slider to change the value
What I want to achieve is letting the user tap on anywhere on the slider and set the current time of the video.
I got an approach from this answer, and I tried to apply it to my case, but couldn't make it work
class ViewController: UIViewController, PlayerDelegate {
var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
// Setup the slider
}
func sliderTapped(gestureRecognizer: UIGestureRecognizer) {
// print("A")
let pointTapped: CGPoint = gestureRecognizer.locationInView(self.view)
let positionOfSlider: CGPoint = slider.frame.origin
let widthOfSlider: CGFloat = slider.frame.size.width
let newValue = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)
slider.setValue(Float(newValue), animated: true)
}
}
I thought this should work, but no luck. Then I tried debugging with trying to printing "A" to logs, but it doesn't so apparently it doesn't go into the sliderTapped()
function.
What am I doing wrong? Or is there a better way to achieve what I am trying to achieve?
Upvotes: 29
Views: 20932
Reputation: 124
For me, @pietor's answer only works if the touch moves enough to trigger a value change. This may be true on actual devices, but in the simulator, it does nothing with a single touch event. The code below works better for me, and is less jerky when the thumb moves to the touch location:
class CustomSlider : UISlider
{
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let track = trackRect(forBounds: bounds)
let thumb = thumbRect(forBounds: bounds, trackRect: track, value: 0.0)
let pos = touch.location(in: self)
let normalizedValue = Float((pos.x - track.minX - thumb.width / 2.0) / (track.width - thumb.width))
self.setValue(minimumValue + (maximumValue - minimumValue) * normalizedValue, animated: true)
return true
}
}
Upvotes: 0
Reputation: 534885
So, taking the problem to be a slider whose value can be changed either by sliding the "thumb" or by tapping on the slider as a whole, and pulling together a number of answers and comments already on this page, we can neatly express the solution like this:
class MySlider: UISlider {
override init(frame: CGRect) {
super.init(frame:frame)
config()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
config()
}
func config() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
addGestureRecognizer(tap)
}
@objc func tapped(_ tapper: UITapGestureRecognizer) {
let pointTappedX = tapper.location(in: self).x
let positionOfSliderX = bounds.origin.x + 15
let widthOfSlider = bounds.size.width - 30
let newX = (pointTappedX - positionOfSliderX) / widthOfSlider
let newValue = newX * CGFloat(maximumValue) + (1 - newX) * CGFloat(minimumValue)
setValue(Float(newValue), animated: true)
sendActions(for: .valueChanged)
}
}
Upvotes: 0
Reputation: 1176
I implemented pteofil's, however, because I already had an action attached to valueChanged
, I was having issues with the tracking.
I Was looking at Adam's answer, and I noticed he had made a very good implementation as well, except that I don't generally like adding gesture recognisers. So I combined the two answer (kinda), and now I can both change the value using sliding (and hence triggering the valueChanged
as well as if tapped on the slider:
UISlider
, and overrode the beginTracking
method: override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
return true //Without this, we don't get any response in the method below
}
touchEnded
method (I guess you could override other states as well, but this seemed to be the best logically-suited one) override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self)
let conversion = minimumValue + Float(location.x / bounds.width) * maximumValue
setValue(conversion, animated: false)
sendActions(for: .valueChanged) //Add this because without this it won't work.
}
I also have my standard IBAction as well elsewhere (this is not relevant to above directly, but I added it in order to provide the full context):
@IBAction func didSkipToTime(_ sender: UISlider) {
print("sender value: \(sender.value)") // Do whatever in this method
}
Hope it will be of some use and fit your need.
Upvotes: 4
Reputation: 1913
I like this approach
extension UISlider {
public func addTapGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
addGestureRecognizer(tap)
}
@objc private func handleTap(_ sender: UITapGestureRecognizer) {
let location = sender.location(in: self)
let percent = minimumValue + Float(location.x / bounds.width) * maximumValue
setValue(percent, animated: true)
sendActions(for: .valueChanged)
}
}
And later just call
let slider = UISlider()
slider.addTapGesture()
Upvotes: 6
Reputation: 1
The probably simplest solution would be, using the "touch up inside" action, connected trough the interface builder.
@IBAction func finishedTouch(_ sender: UISlider) {
finishedMovingSlider(sender)
}
This will get called as soon as your finger leaves the phone screen.
Upvotes: -1
Reputation: 420
This is my code, based on "myuiviews" answer.
I fixed 2 little "bugs" of the original code.
1 - Tapping on 0 was too difficult, so I made it easier
2 - Sliding the slider's thumb just a little bit was also firing the "tapGestureRecognizer", which makes it return to the initial position, so I added a minimum distance filter to avoid that.
Swift 4
class ViewController: UIViewController {
var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
// Setup the slider
// Add a gesture recognizer to the slider
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(sliderTapped(gestureRecognizer:)))
self.slider.addGestureRecognizer(tapGestureRecognizer)
}
@objc func sliderTapped(gestureRecognizer: UIGestureRecognizer) {
// print("A")
var pointTapped: CGPoint = gestureRecognizer.location(in: self.view)
pointTapped.x -= 30 //Subtract left constraint (distance of the slider's origin from "self.view"
let positionOfSlider: CGPoint = slider.frame.origin
let widthOfSlider: CGFloat = slider.frame.size.width
//If tap is too near from the slider thumb, cancel
let thumbPosition = CGFloat((slider.value / slider.maximumValue)) * widthOfSlider
let dif = abs(pointTapped.x - thumbPosition)
let minDistance: CGFloat = 51.0 //You can calibrate this value, but I think this is the maximum distance that tap is recognized
if dif < minDistance {
print("tap too near")
return
}
var newValue: CGFloat
if pointTapped.x < 10 {
newValue = 0 //Easier to set slider to 0
} else {
newValue = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)
}
slider.setValue(Float(newValue), animated: true)
}
}
Upvotes: 2
Reputation: 77
pteofil's answer should be the accepted answer here.
Below is the solution for Xamarin for everyone's interested:
public override bool BeginTracking(UITouch uitouch, UIEvent uievent)
{
return true;
}
As pteofil mentioned, you need to subclass the UISlider for this to work.
Upvotes: 0
Reputation: 4163
It seems like just subclassing UISlider and returning always true to the beginTracking produce the desired effect.
iOS 10 and Swift 3
class CustomSlider: UISlider {
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
return true
}
}
Use the CustomSlider instead of UISlider afterwards in your code.
Upvotes: 43
Reputation: 1261
Looks like you need to actually initialize the tap gesture recognizer in your viewDidLoad() per the code example above. There's a comment there, but I don't see the recognizer being created anywhere.
Swift 2:
class ViewController: UIViewController {
var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
// Setup the slider
// Add a gesture recognizer to the slider
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "sliderTapped:")
self.slider.addGestureRecognizer(tapGestureRecognizer)
}
func sliderTapped(gestureRecognizer: UIGestureRecognizer) {
// print("A")
let pointTapped: CGPoint = gestureRecognizer.locationInView(self.view)
let positionOfSlider: CGPoint = slider.frame.origin
let widthOfSlider: CGFloat = slider.frame.size.width
let newValue = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)
slider.setValue(Float(newValue), animated: true)
}
}
Swift 3:
class ViewController: UIViewController {
var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
// Setup the slider
// Add a gesture recognizer to the slider
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(sliderTapped(gestureRecognizer:)))
self.slider.addGestureRecognizer(tapGestureRecognizer)
}
func sliderTapped(gestureRecognizer: UIGestureRecognizer) {
// print("A")
let pointTapped: CGPoint = gestureRecognizer.location(in: self.view)
let positionOfSlider: CGPoint = slider.frame.origin
let widthOfSlider: CGFloat = slider.frame.size.width
let newValue = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)
slider.setValue(Float(newValue), animated: true)
}
}
Upvotes: 48