我想得到的是UISlider
,它不仅可以让用户在thumbRect
开始时滑动,也可以在其他地方点击时滑动。当用户点击滑块但在thumbRect
之外时,滑块应跳转到该值,然后仍然保持用户的滑动手势。
到目前为止我尝试过的是实现像in this suggestion这样的UIGestureRecognizer的子类。它会在thumbRect
之外的某处发生触碰时启动。问题是滑块设置其值但是由于触摸识别器已经触摸了,因此忽略了进一步的滑动手势。
我如何实现一个滑块,您可以在任何地方点按但仍然可以立即滑动?
编辑:ali59a非常友好地添加我现在所做的an example。这需要再次抬起手指,之后我可以触摸并拖动滑动(水龙头不是我想要的,我需要立即'触摸并滑动'。
答案 0 :(得分:23)
我不确定你是否还在寻找答案,但我今天只是在看这个;我设法让它为我工作。
它的关键是使用UILongPressGestureRecognizer
而不只是UITapGestureRecognizer
,我们可以将识别器的minimumPressDuration
设置为0;使它充当点击识别器,除了你现在可以实际检查它的状态。
只需将UITapGestureRecognizer
替换为UILongPressGestureRecognizer
,将所建议的ali59a设为适合您。但是,我发现这似乎并没有将thumbRect直接放在我的拇指下面。它对我来说有点不合适。
我为我的项目创建了自己的UISlider子类,这就是我为我实现“点击和滑动功能”的方法。
在我的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];
}
我希望这有意义,并帮助你。
答案 1 :(得分:4)
您应该在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
答案 2 :(得分:3)
这是我对上述内容的修改:
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)
}
}
答案 3 :(得分:3)
添加快速版的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)
}
}
答案 4 :(得分:1)
我为包含UISlider
和minimum
的{{1}}子类完成了@DWilliames解决方案。
此外,我在maximumValueImages
之外的区域实现了用户触摸功能(意味着trackArea
或minimum
周围的区域)。触摸这些区域会移动滑块/以间隔更改值。
maximumValueImage
答案 5 :(得分:0)
我没有查看David Williames answer,但我会发布我的解决方案,以防有人正在寻找另一种方法。
Swift 4
首先创建一个自定义UISlider,以便它也能检测到条形图上的触摸:
class CustomSlider: UISlider {
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
return true
}
}
(不要忘记在故事板上将滑块设置为此CustomSlider)
显示滑块的视图控制器的on 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
}
}
}
在你的&#34; sliderTap&#34;选择器方法:
@objc func sliderTap() {
//pause the player, if you want
audioPlayer?.pause()
}
建议:设置播放器&#34; currentTime&#34;在开始玩之前:
private func startPlaying() {
audioPlayer?.currentTime = Double(slider.value)
audioPlayer?.play()
}
答案 6 :(得分:0)
更新 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's any
// custom behaviour going on there and pass the slider
// variable as the parameter, as indicated below
self.sliderValueChanged(slider)
}
}
答案 7 :(得分:0)
我的解决方案很简单:
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
}}
答案 8 :(得分:0)
有被iOS纯粹社区追逐的风险...
这是从David Williames Answer转换而来的Xamarin iOS C#解决方案。
子类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);
}
}
}
答案 9 :(得分:0)
要扩展Khang Azun的答案(快速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
}
答案 10 :(得分:0)
从Apple,
https://developer.apple.com/forums/thread/108317
现在,这在iOS 10和iOS 11上可以正常使用。您可以像往常一样滑动,由于上面的代码,您可以点击滑块,它会自动滑动。但是,在iOS 12中,此操作无效。您必须强行触摸它才能点击以工作
答案 11 :(得分:0)
这是我有效的解决方案:
update(BitFlags::Read | BitFlags::Write);
答案 12 :(得分:0)
这在iOS 13.6和14.0中对我有效
无需添加仅手势即可覆盖beginTracking
这样的功能:
@objc
private func sliderTapped(touch: UITouch) {
let point = touch.location(in: self)
let percentage = Float(point.x / self.bounds.width)
let delta = percentage * (self.maximumValue - self.minimumValue)
let newValue = self.minimumValue + delta
if newValue != self.value {
value = newValue
sendActions(for: .valueChanged)
}
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
sliderTapped(touch: touch)
return true
}