I’ve been playing around with creating a small Cocoa App for the last week or so. Finding good examples and documentation has been a little problematic, so I’ve put together a tiny application as a reference point to get going when starting fresh. It’s built entirely in Python using Cocoa through the PyObjC wrappers. It includes a XIB created with Interface Builder in Xcode. Bundling the App for distribution is a snap using the py2app command in setuptools.
Source on GitHub: simple_pyobjc_cocoa_xib
All the required Python:
The most useful resource for me has been the source available in the PyObjC examples. One thing that took a while to figure out was how the initial NIB and window are loaded when some of the examples start. The Info.plist file generated by py2app includes the NSMainNibFile element which instructs OS X to load the MainMenu NIB when launching by default.
The py2app tutorial has been useful for getting the initial project set up.
Below are the steps I took to create the application with a 5 minute video of the process at the bottom.
The tool used to create a setup.py file is py2applet. It is not in the PATH by default so the full path can be used to run it.
$ /System/Library/Frameworks/Python.framework/Versions/Current/Extras/bin/py2applet --make-setup SimpleXibDemo.py
By default, the setup.py created by py2applet has argv_emulation set to True. This flag enables dragging of files onto our application in OS X. It’s not supported with the latest libraries, and this application that functionality. So it can be removed from the OPTIONS dict.
To include the XIB that will be created later in the application, it’s added to the DATA_FILES list.
DATA_FILES = ['SimpleXibDemo.xib']
The Python Source
The Python source for the application is all in one file, SimpleXibDemo.py. It consists of one class (SimpleXibDemoController) and a __main__ block to get the application up and running.
Inheriting from the NSWindowController class gives the SimpleXibDemoController class the responsibility of managing a window. It handles events like windowDidLoad to get hooks into the windows lifecycle.
The local variable counterTextField is initialized to objc.IBOutlet(). This allows in Interface builder to assign a reference to a Label to it for providing output to the user.
The @objc.IBAction decorators on a couple functions expose them to Interface Builder as so the functions can be called when a Push Button is activated.
Creating a XIB in Interface Builder
In Xcode, click File… New… File… Create and OS X User Interface Window. Save it with the file name that was referenced above in the setup.py; SimpleXibDemo.xib.
Drag three Push Button elements and one Label element into the new window. The buttons text can be changed by double clicking on them.
Add the SimpleXibDemo.py file to Xcode in File… Add Files… Interface Builder will recognize the Python file and find the objc.IBOutlet() and @objc.IBAction elements.
Click on File’s Owner in the left pane with the projects objects and choose the identity inspector tab in the top right. Change the Class value to our Python class SimpleXibDemoController.
Attaching the IBOutlet and IBActions in Interface Builder:
Hold down CTRL and drag from File’s Owner to the Label. Select the counterTextField outlet.
Hold down CTRL and drag from the decrement button to the File’s Owner. Select the decrement: action. Do the same for the increment button, but select the increment: action.
Hold down CTRL and drag fro the Quit button to the Application object and chose the terminate: action.
Run in Alias Mode for Development
While working on the App, it’s convenient to build it in Alias mode. Resources in the build are aliased to source code, so changes can be previewed without going through a whole rebuild.
$ python setup.py py2app -A
Run the application in Aliased mode:
Build for Distribution
The application is built for distribution using the same py2app command, but without the Alias flag.
$ python setup.py py2app
The distributable Application can be found in the dist folder.
Video of the workflow