Global keyboard shortcuts for a Mac tray app - swift

I am developing a Mac app using Swift 4 which only runs as an Agent (it only has a menu in the tray). I'm completely new to Swift.
I want it to respond to keyboard shortcuts, but the basic shortcuts (see picture) only work when the app has focus (in my case, when the menu is being clicked).
So what I need is for my app to respond to system-wide keyboard shortcuts.
I've tried:
HotKey but it doesn't seem to be compatible with Swift 4, or perhaps I can't use Swift Package Manager
this answer but again Swift 4 raises several errors that I couldn't solve
Has anyone done it?

So I ended up getting it to work with HotKey:
by using Carthage as a package manager instead of SPM
and by not using HotKey's example code from their README, which for some reason didn't work for me, but the one in their example app.
It works great!

I made a Swift package that makes this very easy:
import KeyboardShortcuts
extension KeyboardShortcuts.Name {
static let startFiveRounds = Self("startFiveRounds", default: .init(.t, modifiers: [.command, .option]))
}
#main
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
KeyboardShortcuts.onKeyUp(for: .startFiveRounds) {
// …
}
}
}
Even though you already solved your problem, I think this could be useful for other people having the same problem.
I would also recommend letting the user pick their own keyboard shortcut instead of hardcoding it. My package comes with a control where the user can set their preferred shortcut.

Related

Swift UIApplication not in scope

I want to change isIdleTimerDisabled when a specific view appears. In SwiftUI I use
.onAppear { UIApplication.shared.isIdleTimerDisabled = true } .onDisappear { UIApplication.shared.isIdleTimerDisabled = false }
but even with import UIKit I get the warning "Cannot find 'UIApplication' in scope". How can I fix that? I didn't find a solution after searching for more than an hour.
You should not need to import UIKit, it should work with SwiftUI.
The problem may be your target. Is the file used only on an iOS or iPadOS application target?
Or is it also used, for instance, in an extension, like ShareExtension, and so on?
If it is, you cannot access UIApplication within your code. If this code is shared between extensions and app, you should use conditional compilation to avoid this problem.

typeText() is typing inconsistent characters

I'm beginning to dip my toes into Swift alongside writing UI tests and am having a problem with typing text into a textField. Below is the code:
func testLoginUsernameField() {
let app = XCUIApplication()
app.launch()
let username = "testusername2"
let usernameField = app.textFields["username_field"]
XCTAssertTrue(usernameField.exists)
usernameField.tap()
usernameField.typeText(username)
XCTAssertEqual(usernameField.value as! String, username)
}
The problem occurs when I do usernameField.typeText(username). My text continues to write tstusername2 rather than the testusername2.
This issue happens on the simulator when the Hardware Keyboard is enabled.
Disable the hardware keyboard via the menu
Go to I/O -> Keyboard -> Uncheck "Connect Hardware Keyboard" or use the shortcut ⇧⌘K.
Disable the hardware programmatically
If you'd like to disable the hardware keyboard for your Scheme, no matter what simulator you run, refer to this StackOverflow post. I attempted to use other methods to disable the hardware keyboard via the App Delegate but had no luck.

XCUITest application shortcuts

Is there any way of testing the UIApplication shortcuts within XCUITests?
I know that in order to test 3d shortcuts in a simulator you need a trackpad with force touch, but I was wondering if I could write tests that test my shortcuts.
Looks like yes! You'll need to expose a private API, though. Exported XCUIElement header from Facebook's WebDriverAgent here.
Or, if that's the only thing you need, just expose it ala:
#interface XCUIElement (Private)
- (void)forcePress;
#end
And then to force press your icon by going to the springboard and getting your icon element see my answer here.
class Springboard {
// this is another private method you'll need to expose (see linked other answer)
static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")
/// Open the 3d touch menu
class func open3dTouchMenu() {
XCUIApplication().terminate() // this may or may not be necessary depending on your desired function / app state
// Resolve the query for the springboard rather than launching it
springboard.resolve()
// Force delete the app from the springboard
let icon = springboard.icons["MyAppName"]
if icon.exists {
icon.forcePress()
// do something with the menu that comes up
}
}
}
* I have not tested this yet.

OSX/Swift Make Application Useable Anywhere

I have an application written in osx swift, and I need help with a particular behavior/issue. I have the application set to be above all other windows using this code:
self.window!.level = Int(CGWindowLevelForKey(Int32(kCGScreenSaverWindowLevelKey)))
A little issue with it, from what I've seen, is that it does work... but if I go to my launchpad applications menu, the window still shows up. That's exactly how I want the window to behave, but whenever I hit a button within the window, the application resets to the normal desktop to function properly. I have a link below demonstrating this:
https://gyazo.com/f4d05c10ad7b5dbf8b95f3bd2aa23cc4
See how I'm getting taken out of my launchpad and then reset to the normal desktop screen whenever I hit buttons within my application? I just really need to fix this issue.
Is there any info.plist property that I can use to prevent this from happening? Is there any piece of code I could use to make my window use-able everywhere without resetting it to the proper desktop environment? Thanks in advance!
NSFloatingWindowLevel might be a better choice:
import Cocoa
let kCGFloatingWindowLevel: CGWindowLevel =
CGWindowLevelForKey(CGWindowLevelKey.FloatingWindowLevelKey)
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
self.window!.level = Int(kCGFloatingWindowLevel)
}
}
The window level type you are currently using is primarily meant for screensaver windows. If you want a floating palette or window then using the above should give you the desired behavior.
↳ NSWindow : NSFloatingWindowLevel

My App crashes when iCloud status changes

I know there is a similar question but the OP seems to think his problem was only on the simulator and so not such an issue... But I think mine is in both.
I was having a problem adapting an app for iCloud storage (when trying to observe for NSUbiquityIdentityDidChangeNotification). I could not see what the problem was with my code and so...
...after hours of banging my head against a wall I have set up a new project in Xcode (v6.1 6A1046a) to try and narrow it down. It seems it must be a very basic problem indeed...
The only things I have done in this new projects are:
Enable iCloud capabilities for 'Key-value storage' and for 'iCloud
Documents'(using the default container) - no error messages, appears to be set up fine.
Add a label and a button to the provided view controller in IB.
Add an IBOutlet for both and hook up an IBAction to change the label text when pressed.
For clarity, the only code I added to an empty 'Single View Application' template is:
#IBOutlet weak var label: UILabel!
#IBOutlet weak var button: UIButton!
#IBAction func buttonPressed(sender: AnyObject) {
self.label.text = "You pressed the button"
}
So if I run the project from Xcode in the simulator or on the device, or if I run the project directly on the device, it launches fine and the button works properly - as you would expect...
The problem is:
If I am running on the device from Xcode & I go to the Settings->iCloud->iCloud Drive on the device and toggle the switch for my app (from/to either state) the app crashes. I'm getting no feedback except this:
If I am running the app directly on the device and try and toggle iCloud Drive setting, when I go back into the app it appears to restart (the view is being reset, along with the label.text - neither of which happen if I just visit settings without touching the switch).
It also has the habit of freezing my device completely, which is mildly irritating.
I'm new to iCloud development so I guess I may be missing something really basic...
As I say it's an empty project with only a couple of lines of additional code so it's probably not something I HAVE done. Which limits it to something I HAVEN'T done. I am trying to follow as many tutorials as I can find and as much apple documentation as well. I just can't see any obvious steps I may have overlooked.
Unless it's the expected behaviour, or an Apple issue I guess.
Thanks for any help - it's driving me nuts!
iOs kill your app when you change iCloudDrive state. This is standard iOs behavior you can check this with build in applications(Safari for example).