PyObjC ContactsUI Framework
PyObjC provides Python wrappers for Apple's ContactsUI framework on macOS, enabling Python applications to integrate with the system's contact selection interface. This library is part of the larger PyObjC bridge, which facilitates full-featured Cocoa application development in Python. Currently at version 12.1, PyObjC projects are actively maintained with releases often synchronized with major macOS SDK updates.
Warnings
- breaking PyObjC frequently drops support for older Python versions. For example, version 12.0 dropped support for Python 3.9, and version 11.0 dropped Python 3.8. Always check the release notes for minimum Python version requirements before upgrading.
- breaking PyObjC versions are tightly coupled with macOS SDKs. Installing an older PyObjC version with a newer macOS SDK, or vice-versa, can lead to build errors or runtime issues due to missing or changed APIs.
- gotcha Objective-C selectors with colons (e.g., `doSomething:withSomethingElse:`) are translated into Python method names with underscores (e.g., `doSomething_withSomethingElse_`). Each underscore represents an argument.
- gotcha Starting with PyObjC 11.1, the core bridge aligns with `clang`'s Automatic Reference Counting (ARC) documentation for initializer methods. Methods in the 'init' family now correctly steal a reference to `self` and return a new reference, which might change reference counting behavior in complex `alloc().init()` patterns.
- gotcha PyObjC 10.3 introduced changes that could break `__init__` when a class (or its superclass) defines its own `__new__`. Version 10.3.1 partially reverted this, reintroducing the ability to use `__init__` if `__new__` is user-implemented, but not for PyObjC's provided `__new__`.
- deprecated PyObjC removes bindings for macOS frameworks that are deprecated and removed by Apple in newer macOS SDKs. For example, `IMServicePlugIn` bindings were removed in PyObjC 10.0 because the framework was deprecated in macOS 10.13 and removed in macOS 14.
Install
-
pip install pyobjc-framework-contactsui
Imports
- CNContactPickerViewController
from ContactsUI import CNContactPickerViewController
- NSApplication
from AppKit import NSApplication
- NSObject
from Foundation import NSObject
- CNContactPickerDelegate
from ContactsUI import CNContactPickerDelegate
- CNContact
from Contacts import CNContact
Quickstart
import objc
from AppKit import NSApplication, NSApp, NSObject, NSWindow
from ContactsUI import CNContactPickerViewController, CNContactPickerDelegate
from Contacts import CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey
from Foundation import NSLog
class ContactPickerDelegate(NSObject, CNContactPickerDelegate):
def contactPicker_didSelectContact_(self, picker, contact):
NSLog("Selected contact: %@ %@", contact.givenName(), contact.familyName())
for phone_number in contact.phoneNumbers():
NSLog(" Phone: %@", phone_number.value().stringValue())
picker.dismissViewControllerAnimated_completion_(True, None)
NSApp().stop_(None)
def contactPickerDidCancel_(self, picker):
NSLog("Contact picker cancelled")
picker.dismissViewControllerAnimated_completion_(True, None)
NSApp().stop_(None)
def main():
app = NSApplication.sharedApplication()
delegate = ContactPickerDelegate.alloc().init()
picker = CNContactPickerViewController.alloc().init()
picker.setDelegate_(delegate)
picker.setDisplayedPropertyKeys_([CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey])
# Present the picker. In a real app, this would typically be triggered by a button click.
# For a simple script, we'll just present it directly from a dummy window or by itself.
# For a console app, it usually needs a backing NSApplication and a way to present it.
# Creating a minimal window to anchor it for this quickstart example.
from AppKit import NSWindow, NSBorderlessWindowMask, NSBackingStoreBuffered
from Foundation import NSMakeRect
dummy_window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
NSMakeRect(0, 0, 1, 1), NSBorderlessWindowMask, NSBackingStoreBuffered, False
)
dummy_window.orderOut_(None)
app.activateIgnoringOtherApps_(True)
app.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
picker.view(),
dummy_window,
None, # No modal delegate needed for simple dismiss
None,
None
)
# The picker is presented as a sheet, which requires the app run loop.
app.run()
if __name__ == '__main__':
main()