Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Generate UI code from .pyui
-
Hey guys, I’m just posting this here so other people can use it if they made the same mistake I did.
I wrote many programs and later built UI files in Pythonista to complement them. However, when I wrote a math program with UI for my daughter, every change I made required me to share BOTH the .py and .pyui files in order for her to use the updated version.So I wrote this function/method (that is still a work in progress) to simplify this process.
It essentially takes a UI.View() object and generates copy/paste-able code that can be pasted into your .py file to setup and configure the view so you don’t have to load a .pyui file.Small example of code produced from simple .pyui below. Below that is the function.
3 ui.TextField()
3 ui.Label()
2 ui.Button()#_______________________________________________ acInputField = ui.TextField() calculateDistanceButton = ui.Button() label2 = ui.Label() dcInputField = ui.TextField() label1 = ui.Label() arcDistanceOutputField = ui.TextField() label3 = ui.Label() resetFieldsButton = ui.Button() acInputField.name = 'acInputField' acInputField.x = 175.0 acInputField.y = 250.0 acInputField.width = 150.0 acInputField.height = 32.0 acInputField.background_color = (0.250545, 0.250545, 0.250545, 1.0) acInputField.border_width = 0.0 acInputField.tint_color = (0.431645, 0.431645, 0.431645, 1.0) acInputField.font = ('.SFUI-Regular', 17.0) acInputField.text = '' acInputField.autoresizing = 'LRTB' acInputField.alignment = 0 acInputField.flex = 'LRTB' acInputField.corner_radius = 0.0 acInputField.content_mode = 0 calculateDistanceButton.name = 'calculateDistanceButton' calculateDistanceButton.action = calculateDistanceTapped calculateDistanceButton.x = 75.0 calculateDistanceButton.y = 350.0 calculateDistanceButton.width = 250.0 calculateDistanceButton.height = 75.0 calculateDistanceButton.background_color = (0.401961, 0.401961, 0.401961, 1.0) calculateDistanceButton.border_width = 0.0 calculateDistanceButton.tint_color = (0.25, 1.0, 0.25, 1.0) calculateDistanceButton.title = 'Calculate' calculateDistanceButton.font = ('.SFUI-Semibold', 20.0) calculateDistanceButton.autoresizing = 'TB' calculateDistanceButton.flex = 'TB' calculateDistanceButton.corner_radius = 15.0 calculateDistanceButton.content_mode = 0 label2.name = 'label2' label2.x = 25.0 label2.y = 250.0 label2.width = 125.0 label2.height = 32.0 label2.background_color = (0.251634, 0.251634, 0.251634, 1.0) label2.border_width = 0.0 label2.tint_color = (0.25, 0.6084999999999998, 1.0, 1.0) label2.font = ('.SFUI-Regular', 18.0) label2.text = 'VAC' label2.autoresizing = 'LRTB' label2.alignment = 1 label2.flex = 'LRTB' label2.corner_radius = 0.0 label2.line_break_mode = 4 label2.content_mode = 7 label2.number_of_lines = 0 dcInputField.name = 'dcInputField' dcInputField.x = 175.0 dcInputField.y = 210.0 dcInputField.width = 150.0 dcInputField.height = 32.0 dcInputField.background_color = (0.250545, 0.250545, 0.250545, 1.0) dcInputField.border_width = 0.0 dcInputField.tint_color = (0.431645, 0.431645, 0.431645, 1.0) dcInputField.font = ('.SFUI-Regular', 17.0) dcInputField.text = '' dcInputField.autoresizing = 'LRTB' dcInputField.alignment = 0 dcInputField.flex = 'LRTB' dcInputField.corner_radius = 0.0 dcInputField.content_mode = 0 label1.name = 'label1' label1.x = 25.0 label1.y = 210.0 label1.width = 125.0 label1.height = 32.0 label1.background_color = (0.251634, 0.251634, 0.251634, 1.0) label1.border_width = 0.0 label1.tint_color = (0.25, 0.6084999999999998, 1.0, 1.0) label1.font = ('.SFUI-Regular', 18.0) label1.text = 'VDC' label1.autoresizing = 'LRTB' label1.alignment = 1 label1.flex = 'LRTB' label1.corner_radius = 0.0 label1.line_break_mode = 4 label1.content_mode = 7 label1.number_of_lines = 0 arcDistanceOutputField.name = 'arcDistanceOutputField' arcDistanceOutputField.x = 175.0 arcDistanceOutputField.y = 290.0 arcDistanceOutputField.width = 150.0 arcDistanceOutputField.height = 32.0 arcDistanceOutputField.background_color = (0.248366, 0.248366, 0.248366, 1.0) arcDistanceOutputField.border_width = 0.0 arcDistanceOutputField.tint_color = (0.32543625, 0.32543625, 0.32543625, 1.0) arcDistanceOutputField.font = ('.SFUI-Regular', 17.0) arcDistanceOutputField.text = '' arcDistanceOutputField.autoresizing = 'LRTB' arcDistanceOutputField.alignment = 0 arcDistanceOutputField.flex = 'LRTB' arcDistanceOutputField.corner_radius = 0.0 arcDistanceOutputField.content_mode = 0 label3.name = 'label3' label3.x = 25.0 label3.y = 290.0 label3.width = 125.0 label3.height = 32.0 label3.background_color = (0.248366, 0.248366, 0.248366, 1.0) label3.border_width = 0.0 label3.tint_color = (0.25, 0.6084999999999998, 1.0, 1.0) label3.font = ('.SFUI-Regular', 18.0) label3.text = 'Arc Distance' label3.autoresizing = 'LRTB' label3.alignment = 1 label3.flex = 'LRTB' label3.corner_radius = 0.0 label3.line_break_mode = 4 label3.content_mode = 7 label3.number_of_lines = 0 resetFieldsButton.name = 'resetFieldsButton' resetFieldsButton.action = resetFieldsTapped resetFieldsButton.x = 75.0 resetFieldsButton.y = 433.0 resetFieldsButton.width = 250.0 resetFieldsButton.height = 75.0 resetFieldsButton.background_color = (0.401961, 0.401961, 0.401961, 1.0) resetFieldsButton.border_width = 0.0 resetFieldsButton.tint_color = (0.25, 1.0, 0.25, 1.0) resetFieldsButton.title = 'Reset Fields' resetFieldsButton.font = ('.SFUI-Semibold', 20.0) resetFieldsButton.autoresizing = 'TB' resetFieldsButton.flex = 'TB' resetFieldsButton.corner_radius = 15.0 resetFieldsButton.content_mode = 0 fullView.add_subview(acInputField) fullView.add_subview(calculateDistanceButton) fullView.add_subview(label2) fullView.add_subview(dcInputField) fullView.add_subview(label1) fullView.add_subview(arcDistanceOutputField) fullView.add_subview(label3) fullView.add_subview(resetFieldsButton) #_______________________________________________
def generateUserInterfaceCode(viewObject): fullView = viewObject listOfTypes = [] listOfChildren = [] dictOfObjects = {} debugDict = {} listOfObjectTypes = [] for object in fullView.subviews: objectType = object._pyui['class'] text = None action = None autoresizing = None alignment = None flex = None corner_radius = None border_color = None border_width = None background_color = None tint_color = None title = None font = None continuous = None content_mode = None line_break_mode = None number_of_lines = None name = object.name x = object.x y = object.y width = object.width height = object.height try: tint_color = object.tint_color except AttributeError: None try: border_color = object.border_color except AttributeError: None try: border_width = object.border_width except AttributeError: None try: title = f"'{object.title}'" except AttributeError: None try: text = f"'{object.text}'" except AttributeError: None try: font = object.font except AttributeError: None try: autoresizing = f"'{object.autoresizing}'" except AttributeError: None try: flex = f"'{object.flex}'" except AttributeError: None try: alignment = object.alignment except AttributeError: None try: corner_radius = object.corner_radius except AttributeError: None try: background_color = object.background_color except AttributeError: None try: continuous = object.continuous except AttributeError: None try: if object.action != None: action = object.action action = str(action) #action = action.lstrip('<function ') actionList = action.split(' ') action = actionList[1] if object.name == "difficultySlider": action = f"difficultyChanged" else: action = f"{action}Tapped" except AttributeError: None try: line_break_mode = object.line_break_mode except AttributeError: None try: content_mode = object.content_mode except AttributeError: None try: number_of_lines = object.number_of_lines except AttributeError: None dictOfObjects[name] = { 'type' : objectType, 'action' : action, 'x' : x, 'y' : y, 'width' : width, 'height' : height, 'background_color' : background_color, 'border_color' : border_color, 'border_width' : border_width, 'tint_color' : tint_color, 'title' : title, 'font' : font, 'text' : text, 'autoresizing' : autoresizing, 'alignment' : alignment, 'flex' : flex, 'corner_radius' : corner_radius, 'continuous' : continuous, 'line_break_mode' : line_break_mode, 'content_mode' : content_mode, 'number_of_lines' : number_of_lines} continuous = None title = None text = None listOfChildren.append(object) print(f"#{'_' * 47}\n") for k, v in dictOfObjects.items(): print(f"{k} = ui.{v['type']}()") for k, v in dictOfObjects.items(): print(f"{k}.name = '{k}'") for key, value in v.items(): if value != None and key != 'type': print(f"{k}.{key} = {value}") for k, v in dictOfObjects.items(): if k != 'type': print(f"fullView.add_subview({k})") print(f"#{'_' * 47}\n") ```
-
Just great I wish I had known that before. 😍
-
Just change the file extension…
my_file.pyui
—>my_file.json
and you have a valid and easy to transfer json file. Send the .py and .json files to your recipient and request that they renamemy_file.json
—>my_file.pyui
before running the .py file.You could even automate this rename process just before calling
ui.load_view()
like this:from pathlib import Path import ui json_file_path = Path(__file__).with_suffix(".json") pyui_file_path = Path(__file__).with_suffix(".pyui") if json_file_path.is_file() and not pyui_file_path.is_file(): json_file_path.rename(pyui_file_path) v = ui.load_view() v.present('sheet')
-
Perhaps even slicker, just allow your recipient to load the ui directly from
my_file.json
…try: v = ui.load_view() except FileNotFoundError: v = ui.load_view(__file__[:-2] + "json") v.present('sheet')
-
@ccc ... Seriously? Lol. I open every file I’m curious about via Notepad++ just to see what’s goin on..but that’s at work/on a PC.
Without Notepad++ the thought never crossed my mind that maybe the .pyui file contains easily extractable/readable data. It’s even in dict/json format -.-
Well, thanks for the info. I’ll mess around with it and see if I can modify the function to serialize/deserialize the json and generate the copy/paste-able code still. This opens a world of options. Thanks! -
Being a lazy bone, I will probably use ccc‘s json workaround.
You still have more than one .py-file, but no more need for others to manually rename files.
-
You still have more than one .py-file
Only the .py files that call
ui.load_view()
need the workaround. -
@Robert_Tompkins said
via Notepad++
Staying in Pythonista, you can even rename the .pyui into .txt so you can edit it as text only to see its content
And if you want to send only one file, you can always use
load_view_str
by copying/pasting the content into the .pyimport ui pyui_json = ''' [ { "nodes" : [ { "nodes" : [ ], "frame" : "{{80, 104}, {80, 32}}", "class" : "Button", "attributes" : { "uuid" : "B2F85C43-43EC-4980-AABD-045802F910A3", "frame" : "{{80, 104}, {80, 32}}", "title" : "Button", "class" : "Button", "name" : "button1", "font_size" : 15 }, "selected" : true } ], "frame" : "{{0, 0}, {240, 240}}", "class" : "View", "attributes" : { "enabled" : true, "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "flex" : "" }, "selected" : false } ] ''' v = ui.load_view_str(pyui_json) v.present()
-
rename the .pyui into .txt so you can edit it as text only to see its content
Renaming from .pyui to .json also allows you to edit the file in Pythonista with the added advantage of syntax highlighting.