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.
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:
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: