Build your first Mac app with Xcode
Objective-C Swift Python Shell
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
MacTech Demo Objective-C
MacTech Demo Python
MacTech Demo Swift kopia
MacTech Demo Swift


Build your first Mac app with Xcode

Here are the slides (with speaker notes) and the demo app I built during my MacTech 2016 presentation, Build your first Mac app with Xcode. I built the app in Swift during the talk, but I've also added Objective-C and Python versions in this repo.

Things I Didn't Have Time To Cover

Capturing stdout and exit status

To capture the output from your shell script, create an NSPipe, assign that to the NSTask's standardInput, and then read it using the pipe's fileHandleForReading. Exit status is available in terminationStatus.

let scriptProcess = Process()
scriptProcess.launchPath = scriptPath
scriptProcess.arguments = [spokenTextField.stringValue]

let stdoutPipe = Pipe()
scriptProcess.standardOutput = stdoutPipe


print("Script exited with status \(scriptProcess.terminationStatus)")

let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
if let stdout = NSString(data: stdoutData, encoding: String.Encoding.utf8.rawValue) {
    print("Received stdout: \(stdout)")

NB: If the script outputs more data than will fit in the pipe's buffer (which is on the order of 8 kB or so), it'll hang waiting for it to be emptied. For reading large amounts of data, loop over task.isRunning and read to an NSMutableData buffer.

Multithreading (beachballs)

If your script takes more than a couple of seconds to run it's going to block with a beachball. I would recommend adding an NSProgressIndicator (spinner) that you start when the script runs, and well as disabling the button (you'll need an outlet to the button). Then instead of waitUntilExit() add a terminationHandler with a block of code that stops the spinner and re-enables the button. As your terminationHandler code will be executed in a background thread, you have to update your view objects from code wrapped in DispatchQueue.main.async, as only the main program thread can manipulate them.

Speak when pressing return

Add an action for spokenTextField and call speakButtonClicked:

@IBAction func textFieldPressedReturn(_ sender: Any) {

Python Notes

If you try to follow the Xcode notes it'll work in Swift and Objective-C, but in Python you can't use control clicking in the assistant editor to connect outlets and actions. Instead you have to define the IBOutlet() and @IBAction first, add a custom object with the class set to MainWindowController to your xib, and then right click on that to make the connections.

You're also going to want to install the Xcode 6 Cocoa-Python Templates.