UISwitch有时不会发送UIControlEvents

时间:2012-08-22 19:05:52

标签: objective-c ios ios5

我有一个包含布尔标志的模型对象。我想使用UISwitch显示标志的值。可以通过两种方式更改值:

  1. 首先由用户切换开关。我为此注册了UIControlEventTouchUpInside。我也尝试过UIControlEventValueChanged - 它具有完全相同的效果。
  2. 其次是一些外部状态变化。我使用计时器来检查状态变化并相应地设置开关的on属性。
  3. 但是,在timer方法中设置开关的值会导致即使用户触摸了开关,有时也不会触发touchUpInside操作。

    所以我面临以下问题:如果我在外部状态发生变化时在定时器中设置了开关状态,我会从用户那里放松一些状态变化。如果我不使用计时器,我会收到用户的所有状态更改。但是,我错过了所有外部状态变化。

    现在我的想法已经用完了。如何实现我想要的,在模型中获得两种类型的状态更改并在交换机视图中正确反映它们?

    这是一个显示问题的最小示例。我已经用一个简单的布尔标志替换了模型对象,并且在计时器中我根本没有改变标志,我只是调用setOn:animated:。我统计了action方法的调用。像这样我可以很容易地找出错过了多少次触摸:

    #import "BPAppDelegate.h"
    #import "BPViewController.h"
    
    @implementation BPAppDelegate {
        NSTimer *repeatingTimer;
    }
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
    {
        self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        BPViewController *viewController = [[BPViewController alloc] init];
        self.window.rootViewController = viewController;
        [self.window makeKeyAndVisible];
        [self startTimer];
        return YES;
    }
    
    - (void) startTimer {
        repeatingTimer = [NSTimer scheduledTimerWithTimeInterval: 0.2
                                                          target: self.window.rootViewController
                                                        selector: @selector(timerFired:)
                                                        userInfo: nil
                                                         repeats: YES];
    }
    
    @end
    
    
    #import "BPViewController.h"
    
    @implementation BPViewController {
        UISwitch *uiSwitch;
        BOOL value;
        int count;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        value = true;
        uiSwitch = [[UISwitch alloc] init];
        uiSwitch.on = value;
        [uiSwitch addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:uiSwitch];
    }
    
    - (void)touchUpInside: (UISwitch *)sender {
        count++;
        value = !value;
        NSLog(@"touchUpInside: value: %d, switch: %d, count: %d", value, sender.isOn, count);
    }
    
    - (void) timerFired: (NSTimer*) theTimer {
        NSLog(@"timerFired: value: %d, switch: %d, count: %d", value, uiSwitch.isOn, count);
        // set the value according to some external state. For the example just leave it.
        [uiSwitch setOn:value animated:false];
    }
    
    @end
    

4 个答案:

答案 0 :(得分:4)

使用UIControlEventValueChanged确定切换的时间(以编程方式或由用户切换)。不要使用touchUpInside。

另外,不要复制已经由UISwitch维护的属性(例如你的value属性)(即“on”属性)......它是多余的,只会让你遇到麻烦

答案 1 :(得分:2)

编辑:以下代码完全符合您的要求,如果不是您想要的话。如果确保定时器更改和触摸控件的更改都会收到操作方法。该方法仅在每次更改时发送一次,这有望成为您想要的。

注意切换到UIControlEventValueChanged。

[sw addTarget:self action:@selector(touched:) forControlEvents:UIControlEventValueChanged];

- (void) timerFired: (NSTimer*) theTimer
{
    // set the value according to some external state. For the example just leave it.
    BOOL oldOn = sw.on;
    BOOL newOn = YES;

    [sw setOn:newOn];

    if(newOn != oldOn) {
        for(id target in [sw allTargets]) {
            for(NSString *action in [sw actionsForTarget:target forControlEvent:UIControlEventValueChanged]) {
                [target performSelector:NSSelectorFromString(action) withObject:self];
            }
        }
    }
}

EDIT2: 我[原创海报]刚看到你的答案中的新代码并试了一下。我不确定它是否正是我想要的。但是,我不确定我是否理解正确。在这两种情况下我都不想做同样的事情。

  1. 问:当用户触摸开关时,我想切换模型中的布尔值。我不一定需要以编程方式更改开关,因为它无论如何都是通过触摸自动完成的。我想计算一下,确保没有丢失。
  2. 答:嗯,那真的不可能。但是,您当然可以在操作方法中添加一个计数器,并查看您使用的水龙头是否与计数器相同。当用户点击开关时,“sender == theSwitch”。如果您另外发送操作方法,则可以使用其他发件人(区分它们)。

    1. 如果我在最小的例子中得到了计时器事件,我只想保留当前的布尔值。但是,我需要以编程方式设置开关状态,以便视图反映正确的模型状态。 - Bernhard Pieber 3分钟前
    2. 多数民众赞成 - 但你一直说你想要调用动作方法。这段代码就是这样。如果您错误地说明了这一点,并且您不想调用操作方法,则删除“if”块。

      - (void)insureValueIs:(BOOL)val
      {
          BOOL oldVal = sw.on;
          if(val != oldVal) {
              for(id target in [sw allTargets]) {
                  for(NSString *action in [sw actionsForTarget:target forControlEvent:UIControlEventValueChanged]) {
                      [target performSelector:NSSelectorFromString(action) withObject:self];
                  }
              }
          }
      }
      

      当然,我确实想要实现同样的事情:模型状态在视图中正确反映。那有意义吗?

      坦率地说,你没有很好地描述你想要的东西,我一直在回应你的评论,但即使是现在我仍然不完全清楚。我相信你拥有所需的所有信息,可以随心所欲地做任何事情。

答案 2 :(得分:1)

您的计时器经常触发它可能会阻止切换完成动画转换到备用值,因此它永远不会发送valueChanged事件消息。您每隔0.2秒将值设置为YES,我认为这比用户点击开关时动画的持续时间更快。

答案 3 :(得分:0)

我的原始代码在iOS 7中按预期工作。因此它似乎是iOS 6中的一个错误。