Preventing macOS App Nap in Swift 4 - swift

I'm using Swift 4 and Xcode 9.2 to build a timer app and I'm running into issues with macOS App Nap. I know that the issues are caused by App Nap since when I disable App Nap globally the issues go away. However I only want to disable App Nap for this particular app when I need to for it to run properly. I know that similar questions have been answered before and I have seen them, but either I don't understand how to use them in my case or they do not work anymore (possibly because of changes to the Swift language).
Here is a small example of code that produces the issues I'm having:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var timer = Timer()
var seconds = 300
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#IBAction func goButton(_ sender: NSButton) {
if !timer.isValid {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(AppDelegate.ticker), userInfo: nil, repeats: true)
}
}
#objc func ticker() {
seconds -= 1
if seconds == 0 {
timer.invalidate()
print("Timer Ended.")
}
}
}
Any help is greatly appreciated.

Related

Swift Timer lags

I know Timer is not guaranteed to run at exactly the rate you asked for.
However, I have just started an all new project with this:
class ViewController: NSViewController {
var timer = Timer()
var count: Double = 0.0
var timeStamp: Date = Date()
override func viewDidLoad() {
super.viewDidLoad()
self.timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(self.updateCounting),
userInfo: nil,
repeats: true)
}
#objc func updateCounting(){
let duration = self.timeStamp.distance(to: Date())
print("Counting \(self.count) vs. Real life \(duration)")
self.count = self.count + 0.1
}
}
I am expecting to get roughly the same results between count and duration but I see strange things like this:
Counting 60.50000000000059 vs. Real life 60.64057409763336
Counting 60.60000000000059 vs. Real life 60.741435050964355
Counting 60.70000000000059 vs. Real life 70.84065008163452
Counting 60.800000000000594 vs. Real life 80.94094407558441
Counting 60.900000000000595 vs. Real life 82.99448704719543
Tested on Xcode 11.5, on iMac and MacBook Pro.
Am I doing something wrong here?
Not wrong, but you missed the Energy Efficiency Guide for Mac Apps:
Prioritize Work at the App Level
Prioritize Work at the Task Level
Best Practices
Your app was just napping.
Check the ProcessInfo & search for Managing Activities. An example (bad one) is to change your AppDelegate.swift to:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
private var activity: NSObjectProtocol!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let options: ProcessInfo.ActivityOptions = [.userInitiatedAllowingIdleSystemSleep]
let reason = "No napping!"
self.activity = ProcessInfo.processInfo.beginActivity(options: options, reason: reason)
}
func applicationWillTerminate(_ aNotification: Notification) {
ProcessInfo.processInfo.endActivity(activity)
}
}
But don't do this, it's bad for users, MacBook Pro battery life, etc.

AppDelegate#applicationDidFinishLaunching not called for Swift 4 MacOS app built from command line

So here is the full code:
main.swift
import Cocoa
let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
AppDelegate.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
Swift.print("AppDelegate.init")
super.init()
Swift.print("AppDelegate.init2")
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
Swift.print("AppDelegate.applicationDidFinishLaunching")
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
Swift.print("AppDelegate.applicationShouldTerminateAfterLastWindowClosed")
}
}
Then I compile it with:
swiftc Sources/*
./main
It logs:
AppDelegate.init
AppDelegate.init2
But then it doesn't log anything else, so I close it with CTRL+C. Not sure why it's not reaching the applicationDidFinishLaunching method ever. Wondering if one knows of a fix for this, it seems like there is just one method that needs to be called somewhere, but I'm not sure.
I think this may be causing other issues such as NSMenu not working.
Hooray, it was just because of the format of the methods, which were silently failing I guess.
func applicationWillFinishLaunching(_ notification: Notification) {
Swift.print("AppDelegate.applicationWillFinishLaunching")
}
func applicationDidFinishLaunching(_ notification: Notification) {
Swift.print("AppDelegate.applicationDidFinishLaunching")
}

Activate Application on Screen Unlock

I'm writing an application for macOS and I want it to detect when the screen is unlocked and then make itself become the active application.
I'm trying to use "com.apple.screenIsUnlocked", but it doesn't seem to work (the function doesn't even run). I also tried using NSWorkspaceDidWakeNotification where I got the function to run but the app didn't actually activate (presumably because the screen was still locked).
Here is what I currently have (I'm using Xcode 9.2 and Swift 4):
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(AppDelegate.screenDidUnlock), name: NSNotification.Name(rawValue: "com.apple.screenIsUnlocked"), object: nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func screenDidUnlock() {
NSApplication.shared.activate(ignoringOtherApps: true)
print("Did Run")
}
The com.apple.screenIsUnlocked notification is posted to the DistributedNotificationCenter rather than the NSWorkspace's notificationCenter, so the observer should be added like this:
DistributedNotificationCenter.default().addObserver(self, selector: #selector(AppDelegate.screenDidUnlock), name: NSNotification.Name(rawValue: "com.apple.screenIsUnlocked"), object: nil)enter code here
I don't think apple allows you to run any application in the background.

How do I add a timer using Swift?

I have come across a lot of questions similar to this, but many were for older versions of Xcode, or simply did not work.
I'm using Xcode Version 8.3.2 (8E2002) and Swift coding language. I don't know much about coding, but am young and eager to learn!
I'm creating a clicker game that will give you money per second that you are on the game itself. So if you idle for 2 minutes, it would give you $120 ($1per second #120 sec). In addition to this, you also can earn money from clicking the main object.
Here is my coding so far:
import UIKit
class ViewController: UIViewController {
var score = 0
var add = 1
func addpersec() {
score += 1
}
//func used to add to the score based timer. Aka, adding 1 per second
#IBOutlet weak var scorecount: UILabel!
#IBAction func clicks(_ sender: Any) {
score += 1
scorecount.text = "Honey: \(score)"
}
#IBOutlet weak var Bees: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
class ViewController: UIViewController {
var timer: Timer? = nil // Property
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(handleTimer), userInfo: nil, repeats: true)
}
func handleTimer(_ timer: Timer) {
print("Timer ticking!")
}
}
To invalidate the timer, call self.timer?.invalidate()
Your question seems to be related to iOS UI, so I don't know if my answer makes sense.
For general purpose delayed function execution (like Javascript's setTimeout), you can use a DispatchQueue
// have this as a global somewhere
let bg = DispatchQueue(label: "bg", qos: .background, target: nil)
// anywhere else in your code:
// First decide the time to execute your function
let delayInSeconds = 3
let when = DispatchTime.now() + delayInSeconds
// call it
bg.asyncAfter(deadline: when) {
// code to execute later (this is the body of an anonymous function)
}

Making a window visible over the loginwindow using "canBecomeVisibleWithoutLogin" and Swift on OS X 10.11

Hopefully someone can assist me.
I am trying to make a windowed application appear at the loginwindow on OSX 10.11, specifically at logout.
I am calling it using a logouthook script - I can see that the app is called on logout and the delay I have added to the application pauses the logout for 10 seconds but it doesn't actually display the main window.
The main window does display on login and I have tested removing the "canBecomeVisibleWithoutLogin" parameter which causes me to see errors in the system.log relating to the window not having permission to run over the loginwindow. Based on this, I believe the parameter is at least recognised.
I have looked around for examples on the web that use "canBecomeVisibleWithoutLogin" and I haven't been able to determine what step I am missing. I would appreciate any advice.
The code below is the only code I have added to the application which consists of a MainMenu.nib and a AppDelegate.swift.
I have also selected "visible at launch" and "Move to Active Space" in Xcode but this hasn't changed the behaviour at logout.
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationWillFinishLaunching(notification: NSNotification) {
self.window.canBecomeVisibleWithoutLogin = true
self.window.orderFrontRegardless()
self.window.level = Int(CGWindowLevelForKey(CGWindowLevelKey.StatusWindowLevelKey))
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
self.window.display()
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 10 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
//put your code which should be executed with a delay here
NSApplication.sharedApplication().terminate(self)
}
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
Setting the window level differently fixed it and I didn't need any other lines relating to self.window apart from self.window.level=2147483631
I am not sure of the full screen logout window level but I did try a couple of lower numbers and it still worked.
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationWillFinishLaunching(notification: NSNotification) {
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
self.window.level=2147483631
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 10 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
//put your code which should be executed with a delay here
NSApplication.sharedApplication().terminate(self)
}
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
I use this one:
window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
This seems to do the trick here.