iOS7中UISwitch的事件ValueChanged:持续调用是一个bug还是什么?

93

编辑

现在已经在上修复了
不要进行任何调整来修复它。

编辑2

显然,同样的问题在iOS 8.0和8.1中再次发生。

编辑3

现在已经在上修复了
不要进行任何调整来修复它。


今天我在UISwitch的事件ValueChanged:中看到,当我从On切换到Off或者从Off切换到On并且我的手指仍停留在右侧或左侧时,它会连续调用。 我附上了GIF图像以更清楚地显示NSLog。

输入图像描述

我的Value Changed方法是:

- (IBAction)changeSwitch:(id)sender{

    if([sender isOn]){
        NSLog(@"Switch is ON");
    } else{
        NSLog(@"Switch is OFF");
    }
    
}

iOS6的开关代码与我们的期望相同:

enter image description here

那么有人能建议我如何只调用一次其状态为打开或关闭,或者这是一个错误还是什么……?

更新

这是我的演示:

编程添加UISwitch

从XIB添加UISwitch


1
我在模拟器上仍然遇到iOS7.1的错误,尚未尝试设备,正在运行Xcode 5.1.1。 - Fonix
3
我遇到了与7.1.2版iPad相同的问题。 - Hassy
7
我可以看到在 iOS 8.0 和 8.1 版本中,UISwitch 存在相同或类似的问题。 - Sea Coast of Tibet
2
仍然存在于9.1版本中。请大家提交一个与https://openradar.appspot.com/15555929相同的问题报告。这是我们唯一解决此问题的方法。 - Guillaume Algis
1
似乎它已经回到了9.3版本。 - Ky -
显示剩余9条评论
12个回答

45

请查看以下代码:

-(void)viewDidLoad
{
    [super viewDidLoad];    
    UISwitch *mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(130, 235, 0, 0)];    
    [mySwitch addTarget:self action:@selector(changeSwitch:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:mySwitch];
}

- (void)changeSwitch:(id)sender{
    if([sender isOn]){
        NSLog(@"Switch is ON");
    } else{
        NSLog(@"Switch is OFF");
    }
}

谢谢您的回答。正如我所说,我尝试了两种方法并得到了相同的结果。至少我知道如何在代码中和从XIB文件中添加开关了。 - Nitin Gohel

12

你可以使用 UISwitch.selected 属性,以确保当值实际更改时,您的代码仅执行一次。我认为这是一个很好的解决方案,因为它避免了必须子类化或添加新属性的问题。

 //Add action for `ValueChanged`
 [toggleSwitch addTarget:self action:@selector(switchTwisted:) forControlEvents:UIControlEventValueChanged];

 //Handle action
- (void)switchTwisted:(UISwitch *)twistedSwitch
{
    if ([twistedSwitch isOn] && (![twistedSwitch isSelected]))
    {
        [twistedSwitch setSelected:YES];

        //Write code for SwitchON Action
    }
    else if ((![twistedSwitch isOn]) && [twistedSwitch isSelected])
    {
        [twistedSwitch setSelected:NO];

        //Write code for SwitchOFF Action
    }
}

这是用Swift实现的:

func doToggle(switch: UISwitch) {
    if switch.on && !switch.selected {
        switch.selected = true
        // SWITCH ACTUALLY CHANGED -- DO SOMETHING HERE
    } else {
        switch.selected = false
    }
}

6
我觉得这个最简单。 - zekel
我点赞了这个帖子,因为它让我想到了一种类似的解决方案,即在忽略切换逻辑时填充“.tag”值。我需要逻辑有时在开启和关闭模式下触发,所以上述方法并不完全适用。 - davidethell

12

我也遇到了同样的问题。我认为我已经找到了一个简单的解决方法。我们只需要使用一个新的BOOL来存储UISwitch的先前状态,并在我们的IBAction(触发值更改)中使用一个if语句来检查开关的值是否实际发生了变化。

previousValue = FALSE;

[...]

-(IBAction)mySwitchIBAction {
    if(mySwitch.on == previousValue)
        return;
    // resetting the new switch value to the flag
    previousValue = mySwitch.on;
 }

没有更多奇怪的行为了。希望这可以帮到你。


2
这只需要说 if(mySwitch.on == previousValue)。 - Keegan Jay

9
如果您在应用程序中使用了许多开关,那么更改UISwitch的操作方法所在的所有位置的代码将成为问题。您可以创建自定义开关并仅在值更改时处理事件。
CustomSwitch.h
#import <UIKit/UIKit.h>

@interface Care4TodayCustomSwitch : UISwitch
@end

CustomSwitch.m

@interface CustomSwitch(){
    BOOL previousValue;
}
@end

@implementation CustomSwitch



- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        previousValue = self.isOn;
    }
    return self;
}


-(void)awakeFromNib{
    [super awakeFromNib];
    previousValue = self.isOn;
    self.exclusiveTouch = YES;
}


- (void)setOn:(BOOL)on animated:(BOOL)animated{

    [super setOn:on animated:animated];
    previousValue = on;
}


-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{

    if(previousValue != self.isOn){
        for (id targetForEvent in [self allTargets]) {
            for (id actionForEvent in [self actionsForTarget:targetForEvent forControlEvent:UIControlEventValueChanged]) {
                [super sendAction:NSSelectorFromString(actionForEvent) to:targetForEvent forEvent:event];
            }
        }
        previousValue = self.isOn;
    }
}

@end

我们会忽略事件,如果值与更改后的值相同。在storyboard中将CustomSwitch放入所有UISwitch类中。这样可以解决问题,并且仅在值更改时调用目标一次。

这对我很有效。它非常理想,因为它不会在实现文件中手动添加多余的代码,因为它是可重用的,并且将其实现隐藏在类实现内部。这只是良好的设计。但如果这个答案有更多的评论就更好了,因为我真的不明白为什么所有的代码都在那里。 - James C
谢谢,这对我有用。你能再解释一下代码吗?@codester - Swayambhu

7
我收到了许多用户反馈,他们遇到了相同的问题,这可能是UISwitch的一个bug。我刚刚找到了一个临时解决方案,使用一个名为KLSwitchgitHub自定义开关。暂时先使用它,希望苹果在下一个xCode更新中修复这个问题。请点击以下链接获取更多信息:https://github.com/KieranLafferty/KLSwitch

6
如果您不需要立即对开关的值更改做出反应,以下方法可能是一种解决方案:
- (IBAction)switchChanged:(id)sender {
  [NSObject cancelPreviousPerformRequestsWithTarget:self];

  if ([switch isOn]) {
      [self performSelector:@selector(enable) withObject:nil afterDelay:2];
  } else {
      [self performSelector:@selector(disable) withObject:nil afterDelay:2];
  }
}

在 iOS 11.2 中完美运行。在我的情况下,它连续触发了两个事件(状态:关闭,滑动:关闭,第一个事件:打开,第二个事件:关闭),因此0.1秒的延迟对我来说已经足够,并且对用户来说也不会察觉到。 - Paul Semionov

3

截至iOS 9.3 beta版,该问题仍然存在。如果您不介意用户无法将其拖动到开关外部,我认为使用.TouchUpInside而不是.ValueChanged可以可靠地工作。


2

当我将开关与其他行为绑定时,这个问题困扰着我。通常情况下,物品不喜欢从 打开打开 的转变。这里是我的简单解决方案:

@interface MyView : UIView
@parameter (assign) BOOL lastSwitchState;
@parameter (strong) IBOutlet UISwitch *mySwitch;
@end

@implementation MyView

// Standard stuff goes here

- (void)mySetupMethodThatsCalledWhenever
{
    [self.mySwitch addTarget:self action:@selector(switchToggled:) forControlEvents:UIControlEventValueChanged];
}

- (void)switchToggled:(UISwitch *)someSwitch
{
    BOOL newSwitchState = self.mySwitch.on;
    if (newSwitchState == self.lastSwitchState)
    {
        return;
    }
    self.lastSwitchState = newSwitchState;

    // Do your thing
}

请确保每次手动更改mySwitch.on时也设置self.lastSwitchState! :)


2

我仍然在iOS 9.2中遇到同样的问题。

我找到了解决方案,并分享出来,希望能对其他人有所帮助。

  1. Create count variable to trace number of times method got call

    int switchMethodCallCount = 0;
    
  2. Save bool value for switch value

    bool isSwitchOn = No;
    
  3. In Switch's value change method perform desire action for first method call only. When switch value again changes set count value andt bool variable value to default

    - (IBAction)frontCameraCaptureSwitchToggle:(id)sender {
    
    
    
    //This method will be called multiple times if user drags on Switch,
    //But desire action should be perform only on first call of this method
    
    
    //1. 'switchMethodCallCount' variable is maintain to check number of calles to method,
    //2. Action is peform for 'switchMethodCallCount = 1' i.e first call
    //3. When switch value change to another state, 'switchMethodCallCount' is reset and desire action perform
    
    switchMethodCallCount++ ;
    
    //NSLog(@"Count --> %d", switchMethodCallCount);
    
    if (switchMethodCallCount == 1) {
    
    //NSLog(@"**************Perform Acction******************");
    
    isSwitchOn = frontCameraCaptureSwitch.on
    
    [self doStuff];
    
    }
    else
    {
    //NSLog(@"Do not perform");
    
    
    if (frontCameraCaptureSwitch.on != isSwitchOn) {
    
        switchMethodCallCount = 0;
    
        isSwitchOn = frontCameraCaptureSwitch.on
    
        //NSLog(@"Count again start");
    
        //call value change method again 
        [self frontCameraCaptureSwitchToggle:frontCameraCaptureSwitch];
    
    
        }
    }
    
    
    }
    

我也在9.2版本中遇到了这个问题。我实施了你的逻辑,现在它按照我的意图工作了。 - Nick Kohrn

1
这种问题通常是由ValueChanged引起的。你不需要按下按钮来运行函数,这不是一个触摸事件。每当你以编程方式将开关切换到on/off时,值都会发生变化并再次调用IBAction函数。
@RoNiT的答案是正确的:
Swift
func doToggle(switch: UISwitch) {
    if switch.on && !switch.selected {
        switch.selected = true
        // SWITCH ACTUALLY CHANGED -- DO SOMETHING HERE
    } else {
        switch.selected = false
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接