recognizers recognizer gesturerecognizers ios uigesturerecognizer uislider uitapgesturerecognizer

ios - recognizer - ¿Cómo habilitar “tocar y deslizar” en un UISlider?



tap gesture recognizer xamarin forms (10)

A riesgo de ser castigado por la comunidad pura de iOS ...

Aquí hay una solución para Xamarin iOS C # convertida de David Williames Answer .

Subclase UISlider:

[Register(nameof(UISliderCustom))] [DesignTimeVisible(true)] public class UISliderCustom : UISlider { public UISliderCustom(IntPtr handle) : base(handle) { } public UISliderCustom() { // Called when created from code. Initialize(); } public override void AwakeFromNib() { // Called when loaded from xib or storyboard. Initialize(); } void Initialize() { // Common initialization code here. var longPress = new UILongPressGestureRecognizer(tapAndSlide); longPress.MinimumPressDuration = 0; //longPress.CancelsTouchesInView = false; this.AddGestureRecognizer(longPress); this.UserInteractionEnabled = true; } private void tapAndSlide(UILongPressGestureRecognizer gesture) { System.Diagnostics.Debug.WriteLine($"{nameof(UISliderCustom)} RecognizerState {gesture.State}"); // need to propagate events down the chain // I imagine iOS does something similar // for whatever recogniser on the thumb control // It''s not enough to set CancelsTouchesInView because // if clicking on the track away from the thumb control // the thumb gesture recogniser won''t pick it up anyway switch (gesture.State) { case UIGestureRecognizerState.Cancelled: this.SendActionForControlEvents(UIControlEvent.TouchCancel); break; case UIGestureRecognizerState.Began: this.SendActionForControlEvents(UIControlEvent.TouchDown); break; case UIGestureRecognizerState.Changed: this.SendActionForControlEvents(UIControlEvent.ValueChanged); break; case UIGestureRecognizerState.Ended: this.SendActionForControlEvents(UIControlEvent.TouchUpInside); break; case UIGestureRecognizerState.Failed: //? break; case UIGestureRecognizerState.Possible: //? break; } var pt = gesture.LocationInView(this); var thumbWidth = CurrentThumbImage.Size.Width; var value = 0f; if (pt.X <= thumbWidth / 2) { value = this.MinValue; } else if (pt.X >= this.Bounds.Size.Width - thumbWidth / 2) { value = this.MaxValue; } else { var percentage = ((pt.X - thumbWidth / 2) / (this.Bounds.Size.Width - thumbWidth)); var delta = percentage * (this.MaxValue - this.MinValue); value = this.MinValue + (float)delta; } if (gesture.State == UIGestureRecognizerState.Began) { UIView.Animate(0.35, 0, UIViewAnimationOptions.CurveEaseInOut, () => { this.SetValue(value, true); }, null); } else { this.SetValue(value, animated: false); } } }

Lo que quiero obtener es un UISlider que le permite al usuario deslizarse cuando comienza con su thumbRect , pero también cuando toca en otra parte. Cuando el usuario toca el control deslizante pero fuera de thumbRect , el control deslizante debe saltar a ese valor y seguir manteniendo el gesto de deslizamiento del usuario .

Lo que he intentado hasta ahora fue implementar una subclase de UIGestureRecognizer como en esta sugerencia . Comienza justo cuando se produce un toque en algún lugar fuera del thumbRect . El problema es que el control deslizante establece su valor, pero luego se ignoran otros gestos de deslizamiento porque el reconocedor de toma de contacto ha robado el toque .

¿Cómo puedo implementar un control deslizante donde se puede tocar en cualquier lugar pero aún así deslizar de inmediato?

Edit: ali59a fue tan amable de agregar un ejemplo de lo que he hecho ahora. Esto requiere levantar el dedo nuevamente, después de eso puedo tocar y arrastrar para deslizar (un toque no es lo que quiero, necesito un ''toque y deslice'' de inmediato).


Añadiendo una versión rápida de la respuesta de Ali AB.

@IBAction func sliderTappedAction(sender: UITapGestureRecognizer) { if let slider = sender.view as? UISlider { if slider.highlighted { return } let point = sender.locationInView(slider) let percentage = Float(point.x / CGRectGetWidth(slider.bounds)) let delta = percentage * (slider.maximumValue - slider.minimumValue) let value = slider.minimumValue + delta slider.setValue(value, animated: true) } }


Aquí está mi modificación a lo anterior:

class TapUISlider: UISlider { func tapAndSlide(gesture: UILongPressGestureRecognizer) { let pt = gesture.locationInView(self) let thumbWidth = self.thumbRect().size.width var value: Float = 0 if (pt.x <= self.thumbRect().size.width / 2) { value = self.minimumValue } else if (pt.x >= self.bounds.size.width - thumbWidth / 2) { value = self.maximumValue } else { let percentage = Float((pt.x - thumbWidth / 2) / (self.bounds.size.width - thumbWidth)) let delta = percentage * (self.maximumValue - self.minimumValue) value = self.minimumValue + delta } if (gesture.state == UIGestureRecognizerState.Began) { UIView.animateWithDuration(0.35, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { self.setValue(value, animated: true) super.sendActionsForControlEvents(UIControlEvents.ValueChanged) }, completion: nil) } else { self.setValue(value, animated: false) super.sendActionsForControlEvents(UIControlEvents.ValueChanged) } } func thumbRect() -> CGRect { return self.thumbRectForBounds(self.bounds, trackRect: self.bounds, value: self.value) } }


Completé la solución de @DWilliames para una subclase de UISlider contiene maximumValueImages minimum y maximumValueImages .

Además, implementé una funcionalidad para los toques de los usuarios en las áreas fuera de trackArea (significa el área alrededor del minimum o la maximumValueImage ). Tocar estas áreas mueve el control deslizante / cambia el valor en intervalos.

- (void) tapAndSlide: (UILongPressGestureRecognizer*) gesture { CGPoint touchPoint = [gesture locationInView: self]; CGRect trackRect = [self trackRectForBounds: self.bounds]; CGFloat thumbWidth = [self thumbRectForBounds: self.bounds trackRect: trackRect value: self.value].size.width; CGRect trackArea = CGRectMake(trackRect.origin.x, 0, trackRect.size.width, self.bounds.size.height); CGFloat value; if (CGRectContainsPoint(trackArea, touchPoint)) { if (touchPoint.x <= trackArea.origin.x + thumbWidth/2.0) { value = self.minimumValue; } else if (touchPoint.x >= trackArea.origin.x + trackArea.size.width - thumbWidth/2.0) { value = self.maximumValue; } else { CGFloat percentage = (touchPoint.x - trackArea.origin.x - thumbWidth/2.0)/(trackArea.size.width - thumbWidth); CGFloat delta = percentage*(self.maximumValue - self.minimumValue); value = self.minimumValue + delta; } if (value != self.value) { if (gesture.state == UIGestureRecognizerStateBegan) { [UIView animateWithDuration: 0.2 delay: 0 options: UIViewAnimationOptionCurveEaseInOut animations: ^{ [self setValue: value animated: YES]; } completion: ^(BOOL finished) { [self sendActionsForControlEvents: UIControlEventValueChanged]; }]; } else { [self setValue: value animated: YES]; [self sendActionsForControlEvents: UIControlEventValueChanged]; } } } else { if (gesture.state == UIGestureRecognizerStateBegan) { if (touchPoint.x <= trackArea.origin.x) { if (self.value == self.minimumValue) return; value = self.value - 1.5; } else { if (self.value == self.maximumValue) return; value = self.value + 1.5; } CGFloat duration = 0.1; [UIView animateWithDuration: duration delay: 0 options: UIViewAnimationOptionCurveEaseInOut animations: ^{ [self setValue: value animated: YES]; } completion: ^(BOOL finished) { [self sendActionsForControlEvents: UIControlEventValueChanged]; }]; } } }


Convertí la respuesta proporcionada por DWilliames a Swift

Dentro de su vistaDidAppear ()

let longPress = UILongPressGestureRecognizer(target: self.slider, action: Selector("tapAndSlide:")) longPress.minimumPressDuration = 0 self.addGestureRecognizer(longPress)

Archivo de clase

class TapUISlider: UISlider { func tapAndSlide(gesture: UILongPressGestureRecognizer) { let pt = gesture.locationInView(self) let thumbWidth = self.thumbRect().size.width var value: Float = 0 if (pt.x <= self.thumbRect().size.width / 2) { value = self.minimumValue } else if (pt.x >= self.bounds.size.width - thumbWidth / 2) { value = self.maximumValue } else { let percentage = Float((pt.x - thumbWidth / 2) / (self.bounds.size.width - thumbWidth)) let delta = percentage * (self.maximumValue - self.minimumValue) value = self.minimumValue + delta } if (gesture.state == UIGestureRecognizerState.Began) { UIView.animateWithDuration(0.35, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { self.setValue(value, animated: true) super.sendActionsForControlEvents(UIControlEvents.ValueChanged) }, completion: nil) } else { self.setValue(value, animated: false) } } func thumbRect() -> CGRect { return self.thumbRectForBounds(self.bounds, trackRect: self.bounds, value: self.value) } }


Debe agregar un gesto de toque en su UISlider .

Ejemplo:

UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sliderTapped:)]; [_slider addGestureRecognizer:tapGestureRecognizer];

En sliderTapped puede obtener la ubicación y actualizar el valor del control deslizante:

- (void)sliderTapped:(UIGestureRecognizer *)gestureRecognizer { CGPoint pointTaped = [gestureRecognizer locationInView:gestureRecognizer.view]; CGPoint positionOfSlider = _slider.frame.origin; float widthOfSlider = _slider.frame.size.width; float newValue = ((pointTaped.x - positionOfSlider.x) * _slider.maximumValue) / widthOfSlider; [_slider setValue:newValue]; }

Aquí creo un ejemplo: https://github.com/ali59a/tap-and-slide-in-a-UISlider


Mi solución es bastante simple:

class CustomSlider: UISlider { override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { let newValue = <calculated_value> self.setValue(newValue, animated: false) super.sendActions(for: UIControlEvents.valueChanged) return true }}


No estoy seguro de si todavía estás buscando una respuesta para esto, pero hoy solo lo estaba viendo; Y me las arreglé para que funcione para mí.

La clave para ello es utilizar un UILongPressGestureRecognizer lugar de un solo UITapGestureRecognizer , luego podemos establecer el valor minimumPressDuration de minimumPressDuration de minimumPressDuration del reconocedor en 0; Haciéndolo actuar como un reconocedor de toque, excepto que ahora puedes verificar su estado.

Poner lo que ali59a sugirió que funcionará para usted, simplemente reemplazando el UITapGestureRecognizer con un UILongPressGestureRecognizer . Sin embargo, encontré que esto no parecía poner el ThumbRect directamente debajo de mi pulgar. Me pareció un poco extraño.

Creé mi propia subclase UISlider para mi proyecto, y aquí es cómo implementé la "función de toque y deslizar" para mí.

En mi método de init :

UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(tapAndSlide:)]; longPress.minimumPressDuration = 0; [self addGestureRecognizer:longPress];

Entonces mi tapAndSlide: método:

- (void)tapAndSlide:(UILongPressGestureRecognizer*)gesture { CGPoint pt = [gesture locationInView: self]; CGFloat thumbWidth = [self thumbRect].size.width; CGFloat value; if(pt.x <= [self thumbRect].size.width/2.0) value = self.minimumValue; else if(pt.x >= self.bounds.size.width - thumbWidth/2.0) value = self.maximumValue; else { CGFloat percentage = (pt.x - thumbWidth/2.0)/(self.bounds.size.width - thumbWidth); CGFloat delta = percentage * (self.maximumValue - self.minimumValue); value = self.minimumValue + delta; } if(gesture.state == UIGestureRecognizerStateBegan){ [UIView animateWithDuration:0.35 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ [self setValue:value animated:YES]; [super sendActionsForControlEvents:UIControlEventValueChanged]; } completion:nil]; } else [self setValue:value]; if(gesture.state == UIGestureRecognizerStateChanged) [super sendActionsForControlEvents:UIControlEventValueChanged]; }

Donde también usé un método para devolver el marco de mi thumbRect personalizado:

- (CGRect)thumbRect { CGRect trackRect = [self trackRectForBounds:self.bounds]; return [self thumbRectForBounds:self.bounds trackRect:trackRect value:self.value]; }

También tengo mi control deslizante animado a la posición donde el usuario toca primero, durante 0,35 segundos. Lo cual creo que se ve bastante dulce, así que lo incluí en ese código. Si no quieres eso, simplemente prueba esto:

- (void)tapAndSlide:(UILongPressGestureRecognizer*)gesture { CGPoint pt = [gesture locationInView: self]; CGFloat thumbWidth = [self thumbRect].size.width; CGFloat value; if(pt.x <= [self thumbRect].size.width/2.0) value = self.minimumValue; else if(pt.x >= self.bounds.size.width - thumbWidth/2.0) value = self.maximumValue; else { CGFloat percentage = (pt.x - thumbWidth/2.0)/(self.bounds.size.width - thumbWidth); CGFloat delta = percentage * (self.maximumValue - self.minimumValue); value = self.minimumValue + delta; } [self setValue:value]; if(gesture.state == UIGestureRecognizerStateChanged) [super sendActionsForControlEvents:UIControlEventValueChanged]; }

Espero que tenga sentido, y te ayude.


No verifiqué la answer David William, pero publicaré mi solución en caso de que alguien esté buscando otra forma de hacerlo.

Swift 4

Primero cree un UISlider personalizado para que también detecte toques en la barra:

class CustomSlider: UISlider { override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { return true } }

(No olvide configurar su control deslizante para que sea este CustomSlider, en el guión gráfico)

El viewDidLoad en el controlador de vista que muestra el control deslizante:

self.slider.addTarget(self, action: #selector(sliderTap), for: .touchDown)

(Esto solo se usa para pausar el reproductor cuando se mueve el control deslizante)

Luego, en tu acción UISlider:

@IBAction func moveSlider(_ sender: CustomSlider, forEvent event: UIEvent) { if let touchEvent = event.allTouches?.first { switch touchEvent.phase { case .ended, .cancelled, .stationary: //here, start playing if needed startPlaying() default: break } } }

Y en su método selector "sliderTap":

@objc func sliderTap() { //pause the player, if you want audioPlayer?.pause() }

Sugerencia: configura el jugador "currentTime" antes de comenzar a jugar:

private func startPlaying() { audioPlayer?.currentTime = Double(slider.value) audioPlayer?.play() }


Respuesta actualizada de tsji10dra a Swift 4 :

@IBAction func sliderTappedAction(sender: UITapGestureRecognizer) { if let slider = sender.view as? UISlider { if slider.isHighlighted { return } let point = sender.location(in: slider) let percentage = Float(point.x / slider.bounds.size.width) let delta = percentage * (slider.maximumValue - slider.minimumValue) let value = slider.minimumValue + delta slider.setValue(value, animated: true) // also remember to call valueChanged if there''s any // custom behaviour going on there and pass the slider // variable as the parameter, as indicated below self.sliderValueChanged(slider) } }