I have a timer that runs to restart an alarm once it goes off.
alarm = NSTimer.scheduledTimerWithTimeInterval(
60 * minutesConstant + secondsConstant,
target:self,
selector: Selector("endSession"),
userInfo: nil,
repeats:false)
Its selector function sets a mode flag, and calls the original function that set the alarm with new minutes and seconds constants, and also sends a user notification that the session has restarted.
I have a menu item that gets updated with the remaining time
So I've been opening it to check that my alarm does indeed restart, and that the notification shows once it hits zero. It works, but when I have the menu open and it gets down to zero it just stays at 0:00 and the timer doesn't fire until I click off the menu at which point it immediately shows the notification and resets the timer like intended.
How can I force the timer to fire when the menu is open? It's not a big deal but I don't want the user to be confused with the session just hangs if they watch the timer go down.
You just have to add your timer to the main runloop as follow:
Swift 4.2
RunLoop.main.add(timer, forMode: .common)
Swift 3
RunLoop.main.add(alarm, forMode: .commonModes)
Swift 2.x
NSRunLoop.mainRunLoop().addTimer(alarm, forMode: NSRunLoopCommonModes)
I had the same problem, but in MacOS using NSOpenPanel. My problem was a timer used for TCP keep-alive which would not fire, and I would lose connection with my devices if the panel was open too long. My app would reconnect once I closed the panel, but was now in the wrong state. Just adding the code as recommended above fixed the problem.
RunLoop.main.add(timer, forMode: .common)
Related
Im trying to figure out how can I do a live 30 second timer which every 30 second something happens. No matter who opens the app the timer will continue to run even if you just open the app and the timer might be on 20s or 10s or even 1s. And right when it hits zero, I have something that pops up.
I have it done but it's a timer that starts when the app is view is opened and always starts from 30s and if someone else opens the app he also starts from 30s i want it so that who ever opens the view the timer is still running.
I have a paper-button with the on-tap function that opens a paper-dialog that contains a "Accept" paper-button that will close it when clicked.
The problem i'm getting is if depending on my screen resolution, and the dialog's "Accept" button is over the initial button to open the dialog, when clicked, the dialog opens and closes. I'm assuming the on-tap event is being fired to both.
I've tried these 2 methods but they do not seem to help.
event.cancelBubble = true;
event.stopPropagation();
The problem is that a capacitive screens or even mouses can generate multiple tap event on the same spot within a few milisec.
The mouses because a quick change in a high and low voltage (logical 1 and 0) generating an AC signal wich can jump trough on a capacitator (which can be the button two contactor between the air) if the conditions matching. But the onclick event is already catching this case and you does not require to do anything to solve it.
The capacitve screens are capacitators and just rolling your finger should trigger multiple tap events because your skin has different depth of insulation and hard to mark the tap begin and end in some cases.
This physical problem should be solved by the platform, but it is not in every situation currently (but most of the devices are filtering this). Im usally solving this isse with a transparent overlay element wich can catch the pointer events for a little duration so I could catch the "prelling" of a button or the capacitive screen for a few ms.
If a 10-20ms is enough for you then wait a frame in your on-tap function with requestAnimationFrame and then show the dialog. Cheap trick, but it does what it has to, but ultimately you can wait a fix timeout to show the dialog, because you have 100ms to respond a user interaction.
You cannot fix this by manipulating the browser events options though because as I know you dont have option to how much time need to pass until the next same event should happend. But if you wait a frame thats could behave like you add a delay between the events.
I am trying to show a notification window in a Mac application.Something that would come up in the trial version of the application.The window would be unmovable for 30 seconds(and it would have a counter counting down to 0).After 30 seconds it would continue execution.
Here is the code.
_systemNotificationWindow = [[SystemNotificationWindow alloc]initWithWindowNibName:#"SystemNotificationWindow"];
NSLog(#"1111");
[self.systemNotificationWindow setActionDelegate:self];
[self.systemNotificationWindow startTimer:30];
NSLog(#"2222");
[self.systemNotificationWindow showWindow:self];
NSLog(#"3333");
NSLog(#"4444");
The code is stuck at this line
[self.systemNotificationWindow showWindow:self];
It shows the window but neither the timer is working nor the window is going away after 30 seconds.Alsoo 3333 and 4444 are not being printed.
It sounds like you're missing an event loop to control the timer, so I'm guessing that you're displaying the window before an event loop has been created.
You can read more about events here and run loops here.
I'm trying to use CGAssociateMouseAndMouseCursorPosition(NO) in a program. This disconnects the mouse from the on screen cursor when your application is "in the foreground". Unfortunately it also disconnects it when Mission Control or the application switcher or who knows what else comes up.
So far I know:
The application is still active.
The window is still key.
Nothing is sent to the default notification center when these things come up.
The application stops receiving mouse moved events, but an NSEvent addGlobalMonitorForEventsMatchingMask:handler: also does not receive them, which is strange to say the least. It should receive any events not delivered to my application. (I was planning to detect the missing events to know when to associate the mouse again.
So, is there a way to detect when my application is no longer in control, specifically because Mission Control or the switch has taken over? They really expect the mouse to work and I need to restore that association for them.
I share your surprise that a global event monitor isn't seeing the events. In a similar situation, I used a Quartz Event Tap for a similar purpose. The Cocoa global event monitor is quite similar to event taps, so I figured it would work.
I put the tap on kCGAnnotatedSessionEventTap and compared the result from CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) to getpid() to determine when the events were going to another app (e.g. Mission Control or Exposé). (I disable the tab when my app resigns active status, so it should only receive events destined for another app when this sort of overlay UI is presented.)
By the way, you mentioned monitoring the default notification center, but, if there's a notification about Mission Control or the like, it's more likely to come to the distributed notification center (NSDistributedNotificationCenter). So, it's worth checking that.
I needed to check for mission control being active and ended up with an approach along the lines of Ken's answer.
Sharing is caring so here is the smallest sensible complete code that worked for me: (Swift 5)
import Foundation
import AppKit
let dockPid = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.dock").first?.processIdentifier
var eventTargetPid: Int32?
let eventTap = CGEvent.tapCreate(
tap: .cgAnnotatedSessionEventTap,
place: .headInsertEventTap,
options: .listenOnly,
eventsOfInterest: CGEventMask(
(1 << CGEventType.mouseMoved.rawValue)
| (1 << CGEventType.keyDown.rawValue)
),
callback: { (tapProxy, type, event, _:UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? in
// Now, each time the mouse moves this var will receive the event's target pid
eventTargetPid = Int32(event.getIntegerValueField(.eventTargetUnixProcessID))
return nil
},
userInfo: nil
)!
// Add the event tap to our runloop
CFRunLoopAddSource(
CFRunLoopGetCurrent(),
CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0),
.commonModes
)
let periodSeconds = 1.0
// Add a timer for periodic checking
CFRunLoopAddTimer(CFRunLoopGetCurrent(), CFRunLoopTimerCreateWithHandler(
kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + periodSeconds, periodSeconds, 0, 0,
{ timer in
guard eventTargetPid != dockPid else {
print("Dock")
return
}
print("Not dock")
// Do things. This code will not run if the dock is getting events, which seems to always be the case if mission control or command switcher are active
}), .commonModes)
CFRunLoopRun()
This simply checks whether the dock was the one to receive the last event of interest (here that includes mouse movement and key-downs).
It covers most cases, but will report the wrong value between the command switcher or mission-control hiding and the first event being sent to a non-dock app. This is fine in my use-case but could be an issue for other ones.
Also, of course, when the dock at the bottom is active, this will detect that too.
Have you tried asking NSRunningApplication?
I have a simple app that I am creating with a countdown timer that uses a DispatcherTimer for the time base. I have an event handler setup for On_Tick of the DispatcherTimer (set for 1 sec interval). I have three (3) pivot pages using three different instances of AdControl and all are "live" with a real ApplicationID and AdUnitID. This timer is setup on one of the pivot pages.
What I am seeing is that when I open my app and the AdControl starts, after 60 seconds, the adControl wants to refresh. My timer works fine for the first minute, then starts to lose a second every three seconds, like it is missing a tick event (coincidentally when the adcontrol "scrolls" to a new message every three seconds?). I've tried using a background worker for the dispatcherTimer but that did not seem to do anything for me. The code in the event handler is fairly short, with just a couple of "if-then" statements and a few textBlock updates.
Anyone else seen similar issues with the AdControl?
I would say the reason is that the ad control and the timer both want to do something on the UI thread. Thus when the ad control is busy the timer action is blocked during this time. To quote MSDN:
Timers are not guaranteed to execute exactly when the time interval
occurs, but they are guaranteed to not execute before the time
interval occurs. This is because DispatcherTimer operations are placed
on the Dispatcher queue like other operations. When the
DispatcherTimer operation executes is dependent on the other jobs in
the queue and their priorities.
It also explains why using a background worker does not help. As soon as you go back from another thread to the UI thread you have the same problem again. So this issue is basically by design.
Oh and it can also be the other way round. If you would be doing intensive work in the UI thread then the ad control would be blocked. As well as the rest of your UI. This is the reason why you should do as much work as possible in background threads. Maybe the ad control doesn't adhere to this advice.
So far this probably won't help you much. But perhaps it is possible to just use one AdControl and move this from Pivot to Pivot as the user pans around?
I've experienced the same problem with my own timer style app. In my case it only appears to happen when there is animation in the current advertisement.
According to the DispatcherTimer documentation, the delay is expected behaviour, so the solution it to use a different timer... eg System.Threading.Timer
...
//create the timer
var timer = new System.Threading.Timer(
new System.Threading.TimerCallback(TimerTick),
null,
//Set the due time to infinite so the timer wont start immediately
System.Threading.Timeout.Infinite,
0);
//start the timer
timer.Change(0, 1000);
//stop the timer
timer.Change(System.Threading.Timeout.Infinite, 0);
}
void TimerTick(object state)
{
//Dont forget to update the UI on the UI thread.
Dispatcher.BeginInvoke(() =>
{
MyTextBox.Text = "New Text";
});
}
Problem solved!