pyto_ui¶
API Reference¶
- Classes
- Functions
- Constants
- UI Elements Colors
- Standard Colors
- Fixed Colors
- Keyboard Appearance
- Keyboard Type
- Return Key Type
- Auto Capitalization
- Font Text Style
- Font Size
- Button Type
- Gesture Type
- Gesture State
- Touch Type
- Auto Resizing
- Content Mode
- Horizontal Alignment
- Vertical Alignment
- Text Alignment
- Line Break Mode
- Table View Cell Style
- Accessory Type
- Table View Style
- Text Field Border Style
- Button Item Style
- System Item
- Presentation Mode
- Appearance
Getting Started¶
Each item presented on the screen is a View
object. This module contains many View
subclasses.
We can initialize a view like that:
import pyto_ui as ui
view = ui.View()
You can modify the view’s attributes, like background_color
for example:
view.background_color = ui.COLOR_SYSTEM_BACKGROUND
Then, call the show_view()
function to show the view:
ui.show_view(view, ui.PRESENTATION_MODE_SHEET)
A view will be presented, with the system background color, white or black depending on if the device has dark mode enabled or not. It’s important to set our view’s background color because it will be transparent if it’s not set. That looks great on widgets, but not in app.
NOTE: The thread will be blocked until the view is closed, but you can run code on another thread and modify the UI from there:
ui.show_view(view)
print("Closed") # This line will be called after the view is closed.
Now we have an empty view, the root view, we can add other views inside it, like a Button
:
button = ui.Button(title="Hello World!")
button.size = (100, 50)
button.center = (view.width/2, view.height/2)
button.flex = [
ui.FLEXIBLE_TOP_MARGIN,
ui.FLEXIBLE_BOTTOM_MARGIN,
ui.FLEXIBLE_LEFT_MARGIN,
ui.FLEXIBLE_RIGHT_MARGIN
]
view.add_subview(button)
We are creating a button with title “Hello World!”, with 100 as width and 50 as height. We place it at center, and we set flex
to have flexible margins so the button will always stay at center even if the root view will change its size.
To add an action to the button:
def button_pressed(sender):
sender.superview.close()
button.action = button_pressed
We define a function that takes the button as parameter and we pass it to the button’s action
property. The superview
property of the button is the view that contains it. With the close()
function, we close it.
So we have this code:
import pyto_ui as ui
def button_pressed(sender):
sender.superview.close()
view = ui.View()
view.background_color = ui.COLOR_SYSTEM_BACKGROUND
button = ui.Button(title="Hello World!")
button.size = (100, 50)
button.center = (view.width/2, view.height/2)
button.flex = [
ui.FLEXIBLE_TOP_MARGIN,
ui.FLEXIBLE_BOTTOM_MARGIN,
ui.FLEXIBLE_LEFT_MARGIN,
ui.FLEXIBLE_RIGHT_MARGIN
]
button.action = button_pressed
view.add_subview(button)
ui.show_view(view, ui.PRESENTATION_MODE_SHEET)
print("Hello World!")
When the button is clicked, the UI will be closed and “Hello World!” will be printed. UIs can be presented on the Today widget if you set the widget script.
UIKit bridge¶
(Previous knowledge of iOS development with UIKit is needed to follow this tutorial)
PytoUI can show custom UIKit views with the UIKitView
class. Presenting UIViewController
is also possible with show_view_controller()
.
See Objective-C for information about using Objective-C classes in Python.
To use classes from UIKit, we can write the following code:
from UIKit import *
Using UIView¶
In this example, we will create a date picker with UIDatePicker
. Firstly, we will import the needed modules.
import pyto_ui as ui
from UIKit import UIDatePicker
from Foundation import NSObject
from rubicon.objc import objc_method, SEL
from datetime import datetime
Then we subclass UIKitView
to implement a date picker by implementing make_view()
to return an UIDatePicker object. DatePicker.did_change
will be the function called when the selected date changes.
class DatePicker(ui.UIKitView):
did_change = None
def make_view(self):
picker = UIDatePicker.alloc().init()
return picker
We will now create an Objective-C subclass of NSObject
to receive UIDatePicker
events. @objc_method
is the equivalent of @objc
in Swift, it exposes a method to the Objective-C runtime.
The didChange
method converts the selected date from NSDate
to datetime
and calls the callback function (DatePicker.did_change
) with the date as parameter.
PickerDelegate.picker
will be set to an instance of the previously created class.
class PickerDelegate(NSObject):
picker = None
@objc_method
def didChange(self):
if self.picker.did_change is not None:
date = self.objc_picker.date
date = datetime.fromtimestamp(date.timeIntervalSince1970())
self.picker.did_change(date)
In the DatePicker.make_view
method, we’ll set the event handler to the delegate’s didChange
method with addTarget(_:action:forControlEvents:)
.
...
def make_view(self):
picker = UIDatePicker.alloc().init()
delegate = PickerDelegate.alloc().init()
delegate.picker = self
delegate.objc_picker = picker
# 4096 is the value for UIControlEventValueChanged
picker.addTarget(delegate, action=SEL("didChange"), forControlEvents=4096)
return picker
...
Then the date picker is usable as any view because UIKitView
is a subclass of View
.
view = ui.View()
view.background_color = ui.COLOR_SYSTEM_BACKGROUND
def did_change(date):
view.title = str(date)
date_picker = DatePicker()
date_picker.did_change = did_change
date_picker.flex = [
ui.FLEXIBLE_BOTTOM_MARGIN,
ui.FLEXIBLE_TOP_MARGIN,
ui.FLEXIBLE_LEFT_MARGIN,
ui.FLEXIBLE_RIGHT_MARGIN
]
date_picker.center = view.center
view.add_subview(date_picker)
ui.show_view(view, ui.PRESENTATION_MODE_SHEET)
The whole script:
import pyto_ui as ui
from UIKit import UIDatePicker
from Foundation import NSObject
from rubicon.objc import objc_method, SEL
from datetime import datetime
# We subclass ui.UIKitView to implement a date picker
class DatePicker(ui.UIKitView):
did_change = None
# Here we return an UIDatePicker object
def make_view(self):
picker = UIDatePicker.alloc().init()
# We create an Objective-C instance that will respond to the date picker value changed event
delegate = PickerDelegate.alloc().init()
delegate.picker = self
delegate.objc_picker = picker
# 4096 is the value for UIControlEventValueChanged
picker.addTarget(delegate, action=SEL("didChange"), forControlEvents=4096)
return picker
# An Objective-C class for addTarget(_:action:forControlEvents:)
class PickerDelegate(NSObject):
picker = None
@objc_method
def didChange(self):
if self.picker.did_change is not None:
date = self.objc_picker.date
date = datetime.fromtimestamp(date.timeIntervalSince1970())
self.picker.did_change(date)
# Then we can use our date picker as any other view
view = ui.View()
view.background_color = ui.COLOR_SYSTEM_BACKGROUND
def did_change(date):
view.title = str(date)
date_picker = DatePicker()
date_picker.did_change = did_change
date_picker.flex = [
ui.FLEXIBLE_BOTTOM_MARGIN,
ui.FLEXIBLE_TOP_MARGIN,
ui.FLEXIBLE_LEFT_MARGIN,
ui.FLEXIBLE_RIGHT_MARGIN
]
date_picker.center = view.center
view.add_subview(date_picker)
ui.show_view(view, ui.PRESENTATION_MODE_SHEET)
Using UIViewController¶
UIKit View controllers can be presented with show_view_controller()
.
In this example, we will subclass UIViewController
and use the LinkPresentation framework to show the preview of a link.
We need to import the required modules.
from UIKit import *
from LinkPresentation import *
from Foundation import *
from rubicon.objc import *
from mainthread import mainthread
import pyto_ui as ui
Then we can subclass UIViewController
and implement viewDidLoad
like any UIKit app does.
send_super()
from rubicon.objc
is used to call methods from the superclass.
@objc_method
is the equivalent of @objc
in Swift, it exposes a method to the Objective-C runtime.
class MyViewController(UIViewController):
@objc_method
def close(self):
self.dismissViewControllerAnimated(True, completion=None)
@objc_method
def dealloc(self):
self.link_view.release()
@objc_method
def viewDidLoad(self):
send_super(__class__, self, "viewDidLoad")
self.title = "Link"
self.view.backgroundColor = UIColor.systemBackgroundColor()
# 0 is the value for a 'Done' button
done_button = UIBarButtonItem.alloc().initWithBarButtonSystemItem(0, target=self, action=SEL("close"))
self.navigationItem.rightBarButtonItems = [done_button]
We create an LPLinkView
from the LinkPresentation framework and we fetch the metadata. The fetch_handler()
function is a block passed to an Objective-C method, it has to be fully annotated. Mark parameters as ObjCInstance
from rubicon.objc
.
...
@objc_method
def viewDidLoad(self):
...
self.url = NSURL.alloc().initWithString("https://apple.com")
self.link_view = LPLinkView.alloc().initWithURL(self.url)
self.link_view.frame = CGRectMake(0, 0, 200, 000)
self.view.addSubview(self.link_view)
self.fetchMetadata()
@objc_method
def fetchMetadata(self):
@mainthread
def set_metadata(metadata):
self.link_view.setMetadata(metadata)
self.layout()
def fetch_handler(metadata: ObjCInstance, error: ObjCInstance) -> None:
set_metadata(metadata)
provider = LPMetadataProvider.alloc().init().autorelease()
provider.startFetchingMetadataForURL(self.url, completionHandler=fetch_handler)
@objc_method
def layout(self):
self.link_view.sizeToFit()
self.link_view.setCenter(self.view.center)
@objc_method
def viewDidLayoutSubviews(self):
self.layout()
When our View controller is ready, we can show it with show_view_controller()
. mainthread()
is used to call a function in the app’s main thread.
@mainthread
def show():
vc = MyViewController.alloc().init().autorelease()
nav_vc = UINavigationController.alloc().initWithRootViewController(vc).autorelease()
ui.show_view_controller(nav_vc)
show()
The whole script:
from UIKit import *
from LinkPresentation import *
from Foundation import *
from rubicon.objc import *
from mainthread import mainthread
import pyto_ui as ui
# We subclass UIViewController
class MyViewController(UIViewController):
@objc_method
def close(self):
self.dismissViewControllerAnimated(True, completion=None)
@objc_method
def dealloc(self):
self.link_view.release()
# Overriding viewDidLoad
@objc_method
def viewDidLoad(self):
send_super(__class__, self, "viewDidLoad")
self.title = "Link"
self.view.backgroundColor = UIColor.systemBackgroundColor()
# 0 is the value for a 'Done' button
done_button = UIBarButtonItem.alloc().initWithBarButtonSystemItem(0, target=self, action=SEL("close"))
self.navigationItem.rightBarButtonItems = [done_button]
self.url = NSURL.alloc().initWithString("https://apple.com")
self.link_view = LPLinkView.alloc().initWithURL(self.url)
self.link_view.frame = CGRectMake(0, 0, 200, 000)
self.view.addSubview(self.link_view)
self.fetchMetadata()
@objc_method
def fetchMetadata(self):
@mainthread
def set_metadata(metadata):
self.link_view.setMetadata(metadata)
self.layout()
def fetch_handler(metadata: ObjCInstance, error: ObjCInstance) -> None:
set_metadata(metadata)
provider = LPMetadataProvider.alloc().init().autorelease()
provider.startFetchingMetadataForURL(self.url, completionHandler=fetch_handler)
@objc_method
def layout(self):
self.link_view.sizeToFit()
self.link_view.setCenter(self.view.center)
@objc_method
def viewDidLayoutSubviews(self):
self.layout()
@mainthread
def show():
# We initialize our view controller and a navigation controller
# This must be called from the main thread
vc = MyViewController.alloc().init().autorelease()
nav_vc = UINavigationController.alloc().initWithRootViewController(vc).autorelease()
ui.show_view_controller(nav_vc)
show()