Skip to content

macOS + Swift

Created: 2020-12-23 10:16:35 -0800 Modified: 2021-01-23 11:22:02 -0800

  • Objective-C and Swift (reference): they’re both interoperable.
  • Cocoa + Carbon: both are the SDK for macOS itself, but Cocoa is newer. You’ll still have to use Carbon for functionality that doesn’t exist yet in Cocoa.
  • UIKit (reference): this is only for iOS, not macOS.
  • Xcode + VScode (reference): if you don’t want to learn Xcode, you can use VSCode instead.
    • Xcode configuration: if you can’t use VSCode but still want Xcode to behave a little more like VSCode, here are some shortcuts I configured:
      • Set up Duplicate line (reference)
      • Structure → Move Line Up: ⌥↑
      • Structure → Move Line Down: ⌥↓
      • Delete line
      • Home/end
        • Unbind “Move to beginning of document”
        • Unbind “Move to end of document”
        • Remove ⇧Home from “Move to Beginning of Document Extending Selection”
        • Remove ⇧End from “Move to End of Document Extending Selection”
        • Add (not replace) shortcut for Move to Beginning of Text: Home
        • Add (not replace) shortcut for Move to End of Text: End
        • Add (not replace) shortcut for Move to Beginning of Text Extending Selection: ⇧Home
        • Add (not replace) shortcut for Move to End of Text Extending Selection: ⇧End
    • Other Xcode tips
      • ⌃I: format selected text (or just the current line if nothing is selected)
      • ⌘⇧O: quick-open (to find functions/files)
      • Get used to holding ⌃⌘ while trying to navigate. ⌃⌘← and ⌃⌘→ will go back/forward in history, and ⌃⌘Click will navigate to the definition of what you clicked.
      • ⌘ and a number will access the corresponding symbol in the bar at the upper left, e.g. ⌘1 goes to the project navigator, ⌘5 to the error view.
  • addGlobalMonitorForEvents (reference):
    • Requires permissions (reference, reference2): you need to add the parent of your executable (e.g. iTerm) to Settings → Security & Privacy → Accessibility.
      • Xcode: GOODNESS GRACIOUS, I cannot figure out how to get this to work when running through Xcode. For one, the Settings picker only seems to let you specify which version of Xcode to use if you have multiple installed, but then right-clicking → “Show in Finder” may point to one that you didn’t choose. Regardless, even if you specify the full path to the correct Xcode binary, it still doesn’t actually give your program the permission, likely because Xcode isn’t the immediate parent of your program.
        • As a workaround, just add your terminal (e.g. Terminal, iTerm) to the Accessibility settings and invoke it from there. Alternatively, you could test through Xcode but change the function call from “addGlobal” to “addLocal” (the signature changes slightly too).
      • Finding your binary: run it through Xcode, then do “ps -ax | grep -i NAME_OF_EXECUTABLE” to find the path.
      • Input Monitoring (reference): in my testing, I didn’t need anything to be in Settings → Security & Privacy → Input Monitoring, but that may have only been because I use Karabiner (although I find this unlikely since I tried testing that too). Thus, you may have to add your program there.
    • Not called locally: this doesn’t get called for local events (i.e. you’ll explicitly need the addLocalMonitorForEvents if you want to handle events that originate from the application).
  • Accessibility-trustedness (reference): TextExpander doesn’t need to be added to the Accessibility settings probably thanks to using outdated APIs.
  • Sending keystrokes: from my tests on Catalina, I could send ⌘V just by making a CGEvent whose flags are set to CGEventFlags.maskCommand. I did not have to send four events for command-down, V-down, V-up, and command-up.
  • Simple code to run an AppleScript (reference):
let myAppleScript = "tell application \Google Chrome\ to activate"
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: myAppleScript) {
if let outputString = scriptObject.executeAndReturnError(&error).stringValue {
print(outputString)
} else if (error != nil) {
print("error: ", error!)
}
}
  • If you get an error about the application not running (reference), then it probably means your entitlements are messed up. In Xcode, press ⌘⇧O → type “entitlements” to go to your entitlements file. Then, either add an entitlement array called “com.apple.security.temporary-exception.apple-events” with a value for “com.google.Chrome” or disable the App Sandbox.

  • Simple key-listener code (reference):

func keyDown(event: NSEvent!) -> NSEvent {
NSLog("key down is \event.keyCode)")
return event
}
NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask.keyDown, handler: keyDown)
  • Keycodes (reference, reference2): this’ll let you figure out which keys the numerical events correspond to
    • Code for keycodes (reference): simply “import Carbon”, then you can directly use the keycodes:
print("kVK_ANSI_V \kVK_ANSI_V) kVK_Command \kVK_Command)") // prints 9 55
  • New? If you’re new to Swift but not new to coding in general, the guided tour is great.
    • Other resources
  • Quick properties:
    • Optional semicolons
    • Inferred types: no need to explicitly specify a type in “let implicitInteger = 70”)
    • No implicit conversion: even when concatenating strings, you have to explicitly specify types: let widthLabel = “The width is ” + String(width)
    • Null value: it’s “nil” (without quotes)
    • Optional values with question mark:
      • Definition: var optionalString: String? = “Hello” - you could later set this to nil and it would be valid for its type since it’s optional.
      • Default value: print(“Value: \optionalString ?? “default value”)“)
    • Force-unwrap with ”!” (exclamation mark) (reference): this will force optionals to be defined. It’s for when you know an operation will succeed, e.g. “let randomNumber = (1…10).randomElement()!“.
  • String interpolation (reference): use a backslash and parens: “I have \numApples) apples.”
  • Arrays/dictionaries
    • Square brackets for arrays/dicts (reference): var ageDict = [“Allen”: 5, “Bob”: 10]
    • Empty array/dict: var emptyArray = [String]() var emptyDict = [String: Float]()
    • Arrays grow automatically: they’re like Python arrays with “.append”
  • var vs. const (reference): “let” is for constants, “var” for variables.
  • Specifying types: let explicitConstDouble: Double = 70
  • No implicit compare-to-zero: “if numPeople” is not a valid boolean expression, use “if numPeople > 0”.
  • If-scoped values:
if let name = optionalName {
// "name" is accessible here
}
// "name" is not accessible here
  • Switch/case:
    • Exhaustive: switches must be exhaustive, which typically means having a “default” case, but if you were doing a switch on a Boolean, for example, you could just have true/false cases to cover everything.
    • No need for “break”: pretty standard for modern languages—a case requires an explicit “fallthrough” directive in order to move downward to the next “case”.
    • Cases can be expressive: sample from here:
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \x)?")
default:
print("Everything tastes good in soup.")
}
// Prints "Is it a spicy red pepper?"
  • Iterating over dictionary:
let ageDict = ["Allen": 5, "Bob": 10]
for (name, num) in ageDict {
// …
}
  • Iterating over numerical ranges:
print("Counting up")
for i in 0..<4 {
print(i)
}
print("Counting down")
for index in stride(from: 3, to: -1, by: -1) {
print(index)
}

Prints 0 1 2 3 and then 3 2 1 0

  • Simple syntax example:
func greet(person: String, day: String) -> String {
return "Hello \person), today is \day)."
}
// Call the function
greet(person: "Bob", day: "Tuesday")
  • Multiple return values: use a tuple, e.g. this signature:
func calculateStatistics() -> (min: Int, max: Int, sum: Int)
  • …then, to actually return this tuple, it looks like Python:
return (someInt, 6, 22)
  • Anonymous closure syntax: use curly braces, e.g.
numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})

or

numbers.map({ number in 3 * number })