pw2
pw2

Reputation: 386

How to prevent mouse event to freeze timers

I have a very simple macOS app that runs a NSTimer to update a value and display it on a NSTextField on my NSView.

The NSView also contains NSButton and NSSlider controls, not related to that timer.

When I hold mouse button on a NSButton or NSSlider, the timer does not update anymore, until I lift the mouse button (and then the timer resume).

How do I prevent the mouse button events to freeze timer?

The timer should not freeze when I hit and hold mouse button on the NSButton and NSSlider controls.

UPDATE:

I've found a solution, I need to subclass any NSButton and NSSlider objects, with empty mouse event functions like this:

  override func mouseUp(with event: NSEvent) {}

(those functions are a lot).

Mouse event will not bother NSTimer anymore.

But this solution is tricky, I'll go for the suggested solution.

Upvotes: 1

Views: 42

Answers (1)

Rob
Rob

Reputation: 438152

It is a question of the run loop modes that you are using for the timer:

  • The “default” run loop modes will not let the timer fire while the user is interacting with the slider (or whatever). E.g., in Objective-C:

    - (void)startUpdatingDefaultRunMode {
        typeof(self) __weak weakSelf = self;
        [NSTimer scheduledTimerWithTimeInterval:0.02 repeats:true block:^(NSTimer * _Nonnull timer) {
            typeof(self) strongSelf = weakSelf;
            if (!strongSelf) {
                [timer invalidate];
                return;
            }
            strongSelf.label.stringValue = [strongSelf.dateFormatter stringFromDate:[NSDate now]];
        }];
    }
    

    Or Swift:

    func startUpdatingDefaultRunMode() {
        Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { [weak self] timer in
            guard let self else { 
                timer.invalidate()
                return
            }
            label.stringValue = dateFormatter.string(from: .now)
        }
    }
    

    Resulting in:

    enter image description here

  • But if you use “common” run loop modes, the timer will continue to fire. In Objective-C:

    - (void)startUpdatingCommonRunMode {
        typeof(self) __weak weakSelf = self;
        NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:0.02 repeats:true block:^(NSTimer * _Nonnull timer) {
            typeof(self) strongSelf = weakSelf;
            if (!strongSelf) {
                [timer invalidate];
                return;
            }
            strongSelf.label.stringValue = [strongSelf.dateFormatter stringFromDate:[NSDate now]];
        }];
        [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    

    Or Swift:

    func startUpdatingCommonRunMode() {
        let timer = Timer(fire: .now, interval: 0.02, repeats: true) { [weak self] timer in
            guard let self else {
                timer.invalidate()
                return
            }
            label.stringValue = dateFormatter.string(from: .now)
        }
        RunLoop.main.add(timer, forMode: .common)
    }
    

    Resulting in:

    enter image description here

Upvotes: 2

Related Questions