Как включить "коснуться и сдвинуть" в UISlider?
То, что я хочу получить, - это UISlider
, который позволяет пользователю не только сползать, когда он запускается на thumbRect
, но также и когда он вступает в другое место. Когда пользователь нажимает на ползунок, но за пределами thumbRect
, ползунок должен перейти к этому значению , а затем все еще придерживаться скольжения пользователя.
То, что я пробовал до сих пор, заключается в реализации подкласса UIGestureRecognizer, такого как в этом предложении. Он начинается прямо тогда, когда происходит касание где-то вне thumbRect
. Проблема заключается в том, что слайдер устанавливает значение , но затем дальнейшие сдвиговые жесты игнорируются, потому что распознающий сенсорный захват украл прикосновение.
Как я могу реализовать слайдер, где вы можете нажимать в любом месте, но все же слайд сразу?
Изменить: ali59a так любезно добавил пример того, что я сделал сейчас. Для этого нужно снова поднять палец, после чего я могу прикоснуться и перетащить на слайд (крана не то, что я хочу, мне нужно "прикоснуться и сдвинуть" сразу).
Ответы
Ответ 1
Я не уверен, что вы все еще ищете ответ для этого, но я просто смотрел на это сам сегодня; и мне удалось заставить его работать для меня.
Ключ к нему использует UILongPressGestureRecognizer
вместо UITapGestureRecognizer
, тогда мы можем установить minimumPressDuration
распознавателя на 0; что делает его действительным в качестве распознавателя крана, за исключением того, что вы можете теперь фактически проверить его состояние.
Полагая, что предложенный ali59a будет работать для вас, просто заменив UITapGestureRecognizer
на UILongPressGestureRecognizer
. Тем не менее, я обнаружил, что это, похоже, не помещало thumbRect прямо под моим пальцем. Мне это показалось немного.
Я создал свой собственный подкласс UISlider для своего проекта, и вот как я применил для него функцию "tap and slide".
В моем методе init
:
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(tapAndSlide:)];
longPress.minimumPressDuration = 0;
[self addGestureRecognizer:longPress];
Затем мой метод tapAndSlide:
:
- (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];
}
Где я также использовал метод для возврата кадра моего настраиваемого thumbRect:
- (CGRect)thumbRect {
CGRect trackRect = [self trackRectForBounds:self.bounds];
return [self thumbRectForBounds:self.bounds trackRect:trackRect value:self.value];
}
У меня также есть мой слайдер, приближающийся к позиции, в которой пользователь сначала забирает, более 0,35 секунды. Я считаю, что это очень мило, поэтому я включил это в этот код.
Если вы этого не хотите, попробуйте это:
- (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];
}
Я надеюсь, что это имеет смысл и помогает вам.
Ответ 2
Я преобразовал ответ, предоставленный DWilliames, в Swift
Внутри вашего видаDidAppear()
let longPress = UILongPressGestureRecognizer(target: self.slider, action: Selector("tapAndSlide:"))
longPress.minimumPressDuration = 0
self.addGestureRecognizer(longPress)
Файл класса
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)
}
}
Ответ 3
Вы должны добавить жестов кран на UISlider
.
Пример:
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sliderTapped:)];
[_slider addGestureRecognizer:tapGestureRecognizer];
В sliderTapped вы можете получить местоположение и обновить значение ползунка:
- (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];
}
Я создаю пример здесь: https://github.com/ali59a/tap-and-slide-in-a-UISlider
Ответ 4
Здесь моя модификация выше:
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)
}
}
Ответ 5
Добавление быстрой версии 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)
}
}
Ответ 6
Я завершил решение @DWilliames для подкласса UISlider
, содержащего minimum
и maximumValueImages
.
Кроме того, я реализовал функциональность для пользовательских касаний в областях вне trackArea
(означает либо область вокруг minimum
, либо maximumValueImage
). Прикосновение к этим областям перемещает слайдер/изменяет значение в интервалах.
- (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];
}];
}
}
}
Ответ 7
Обновлен ответ tsji10dra на 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 any
// custom behaviour going on there and pass the slider
// variable as the parameter, as indicated below
self.sliderValueChanged(slider)
}
}
Ответ 8
Мое решение довольно просто:
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
}}
Ответ 9
Я не проверял ответ Дэвида Уильямса , но я опубликую свое решение, если кто-то ищет другой способ сделать это.
Swift 4
Сначала создайте пользовательский UISlider, чтобы он обнаруживал штрихи на панели:
class CustomSlider: UISlider {
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
return true
}
}
(не забудьте установить ползунок для этого CustomSlider, на раскадровке)
В представлении ViewDidLoad контроллера просмотра, отображающего ползунок:
self.slider.addTarget(self, action: #selector(sliderTap), for: .touchDown)
(это используется только для приостановки воспроизведения проигрывателя при перемещении слайдера)
Затем в вашем действии 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
}
}
}
И в вашем селекторном методе "sliderTap":
@objc func sliderTap() {
//pause the player, if you want
audioPlayer?.pause()
}
Предложение: установите плеер "currentTime" перед началом воспроизведения:
private func startPlaying() {
audioPlayer?.currentTime = Double(slider.value)
audioPlayer?.play()
}
Ответ 10
Рискуя быть наказанным чистым сообществом iOS...
Вот решение для Xamarin iOS С#, преобразованного из ответа Дэвида Уильямса.
Sub class 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 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);
}
}
}
Ответ 11
Чтобы расширить ответ Кханга Azun- для swift 5, поместите в пользовательский класс UISlider следующее:
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let percent = Float(touch.location(in: self).x / bounds.size.width)
let delta = percent * (maximumValue - minimumValue)
let newValue = minimumValue + delta
self.setValue(newValue, animated: false)
super.sendActions(for: UIControl.Event.valueChanged)
return true
}