1

ブール値フラグを含むモデル オブジェクトがあります。UISwitch を使用してフラグの値を表示したい。値は次の 2 つの方法で変更できます。

  1. まずユーザーがスイッチを切り替えます。そのために UIControlEventTouchUpInside を登録します。UIControlEventValueChanged も試しましたが、まったく同じ効果があります。
  2. 第二に、何らかの外部状態の変化によるものです。タイマーを使用してその状態の変化をチェックし、それに応じてスイッチの on プロパティを設定します。

ただし、タイマー メソッドでスイッチの値を設定すると、ユーザーがスイッチに触れても 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 によって既に維持されているプロパティ (値プロパティなど) (つまり、「on」プロパティ) を複製しないでください。冗長であり、問​​題が発生するだけです。

于 2012-08-22T19:11:45.260 に答える
2

編集:次のコードは、希望どおりではないにしても、希望どおりに機能します。タイマーの変更とコントロールへのタッチの変更の両方がアクション メソッドを受け取ることを保証する場合。メソッドは、変更ごとに 1 回だけ送信されます。これが望ましいことです。

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. Q: ユーザーがスイッチに触れたときに、モデルのブール値を切り替えたいと思います。とにかく触れることで自動的に行われるため、必ずしもプログラムでスイッチを変更する必要はありません。どれも失われないように、タッチを数えたいと思います。

A: うーん、それは実際には不可能です。ただし、確かにカウンターをアクション メソッドに追加して、タップがカウンターと等しいかどうかを確認できます。ユーザーがスイッチをタップすると、"sender == theSwitch" になります。アクション メソッドを別の方法で送信する場合は、別の送信者を使用できます (それらを区別するため)。

  1. 最小限の例でタイマーイベントを取得した場合、ブール値を現在のように残したいだけです。ただし、ビューが正しいモデルの状態を反映するように、スイッチの状態をプログラムで設定する必要があります。– Bernhard Pieber 3分前

それは問題ありませんが、アクション メソッドを呼び出したいと言い続けています。このコードはそれを行います。言い間違えていて、アクション メソッドを呼び出したくない場合は、"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イベントメッセージを送信することはありません。0.2秒ごとに値をYESに設定しています。これは、ユーザーがスイッチをタップしたときに発生するアニメーションの継続時間よりも速いと思います。

于 2012-08-22T19:55:14.893 に答える
0

私の元のコードは iOS 7 で期待どおりに動作します。つまり、iOS 6 のバグだったようです。

于 2014-06-28T15:11:53.583 に答える