1

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

  1. 首先由用户通过切换开关。我为此注册了 UIControlEventTouchUpInside。我还尝试了 UIControlEventValueChanged - 它具有完全相同的效果。
  2. 其次是一些外部状态的变化。我使用计时器来检查该状态更改并相应地设置开关的 on 属性。

但是,在 timer 方法中设置开关的值会导致即使用户触摸了开关,有时也不会触发 touchUpInside 动作。

所以我面临以下问题:如果我在状态外部更改时在计时器中设置开关状态,我会丢失用户的一些状态更改。如果我不使用计时器,我会从用户那里获得所有状态更改。但是,我错过了所有外部状态更改。

现在我已经没有想法了。我怎样才能实现我想要的,在模型中获取两种类型的状态更改并在切换视图中正确反映它们?

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

#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

4 回答 4

5

用于UIControlEventValueChanged确定何时切换开关(以编程方式或由用户)。不要使用touchUpInside. 当用户拖动UISwitch时也touchUpInside不能很好地工作。

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

于 2012-08-22T19:11:45.260 回答
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. 问:当用户触摸开关时,我想切换模型中的布尔值。我不一定需要以编程方式更改开关,因为无论如何它都是通过触摸它自动完成的。我想计算触摸以确保没有丢失。

A:嗯,那是不可能的。但是,您当然可以在操作方法中添加一个计数器,并查看您所做的点击是否等于该计数器。当用户点击开关时,“sender == theSwitch”。如果您以其他方式发送操作方法,则可以使用不同的发送者(以区分它们)。

  1. 如果我在我的最小示例中获得计时器事件,我只想保留当前的布尔值。但是,我需要以编程方式设置开关状态,以便视图反映正确的模型状态。– Bernhard Pieber 3 分钟前

很好 - 但你一直说你想要调用 action 方法。这段代码就是这样做的。如果你说错了,并且你不想调用 action 方法,那么删除“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];
            }
        }
    }
}

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

坦率地说,你没有很好地描述你想要什么,我一直在回复你的评论,但直到现在我仍然不完全清楚。我相信您在这里拥有所需的所有信息,可以做任何您想做的事情。

于 2012-08-22T19:11:08.280 回答
1

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

于 2012-08-22T19:55:14.893 回答
0

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

于 2014-06-28T15:11:53.583 回答