Forum Archive

Pythonista View (question)

DavinE

Hello Guys,

I have an Problem with my view on different devices...
i used fix position with calculator to get it better but it looks not really good...

how can i fix it to do it better look or how is it correct to script that..
i have no idea.... i hope you guys can help me..

in my Pic you see two different iPad devices... and maybe its better to understand ;)

center
too far left

mikael

@DavinE, I assume you are talking about the buttons at the bottom.

Without any of your code, all I can suggest is using the center property for placement, after making sure that the width has been set. And calculating the position using the bounds of the parent view, not the frame.

Where are you doing the placement? In layout?

DavinE

for the better understanding i create a text Page how i place my UI Elements...
but i need to create for every Device parameters....

heres my example:

import ui
from objc_util import ObjCClass, UIColor, ObjCInstance

view = ui.View(name='Barcode Scanner', bg_color='#3664a8')


SCREEN = ui.get_screen_size()
if min(SCREEN) >= 768:
    # iPad
    WIDTH = ''
    for i in SCREEN:
        if not WIDTH:
            WIDTH = i
        else:
            HEIGHT = i
else:
    # iPhone
    WIDTH = ''
    for i in SCREEN:
        if not WIDTH:
            WIDTH = i
        else:
            HEIGHT = i

def get_font():
    return 'Optima'

# Senkrecht
# Label - Willkommen
lb1_width = (WIDTH - 50)
lb1_height = 32
lb1_y = 0.84
lb1_font_size = 22

# TextView - Benutzercode angabe
tv1_width = (WIDTH - 50)
tv1_height = 60
tv1_y = 19.09
tv1_font_size = 18

# SegmentedControl - Scanner|Manuell
sc1_width = (WIDTH - 50)
sc1_height = 30
sc1_y = 30.09
sc1_font_size = 17

# Label - debugMode
lb1_1_width = 350
lb1_1_height = 32
lb1_1_y = 48.09
lb1_1_font_size = 16

# Switch - debugMode Aus/Ein
sw1_width = 51
sw1_height = 31
sw1_y = 53.42

# Button - Benutzercode Abfrage Starten
bt1_width = 300
bt1_height = 30
bt1_y = 80.42
bt1_font_size = 18

lb1 = ui.Label(name='Label1', bg_color='#3664a8')
lb1.text_color = 'white'
lb1.width = lb1_width
lb1.height = lb1_height
lb1.alignment = 1
lb1.text = 'Willkommen auf der Startseite'
lb1.x = ((WIDTH - lb1.width) / 2)
lb1.y = ((HEIGHT * lb1_y) / 100)
lb1.font = (get_font(), lb1_font_size)

tv1 = ui.TextView(name='textview1', bg_color='#3664a8')
tv1.text_color = 'white'
tv1.width = tv1_width
tv1.height = tv1_height
tv1.alignment = 1
tv1.text = 'Bitte gib deinen Benutzer Code an.\nAngabe über:'
tv1.x = ((WIDTH - tv1.width) / 2)
tv1.y = ((HEIGHT * tv1_y) / 100)
tv1.font = (get_font(), tv1_font_size)
tv1.editable = False

sc1 = ui.SegmentedControl(name='Barcode_input', bg_color='#3664a8')
sc1.corner_radius = 20
sc1.width = sc1_width
sc1.height = sc1_height
sc1.x = ((WIDTH - sc1.width) / 2)
sc1.y = ((HEIGHT * sc1_y) / 100)
UIFont = ObjCClass('UIFont').fontWithName_size_(get_font(), sc1_font_size)
Color = UIColor.orangeColor()
attributes = {'NSFont': UIFont, 'NSColor': Color}
setFont = ObjCInstance(sc1).segmentedControl()
setFont.setTitleTextAttributes_forState_(attributes, 0)
sc1.segments = ('Scanner', 'Manuell')
sc1.selected_index = 0

lb1_1 = ui.Label(name='Label1_1', bg_color='#3664a8')
lb1_1.text_color = 'white'
lb1_1.width = lb1_1_width
lb1_1.height = lb1_1_height
lb1_1.alignment = 1
lb1_1.text = 'debugMode {AUS / EIN}'
lb1_1.x = ((WIDTH - lb1_1.width) / 2)
lb1_1.y = ((HEIGHT * lb1_1_y) / 100)
lb1_1.font = (get_font(), lb1_1_font_size)

sw1 = ui.Switch(name='debugMode')
sw1.tint_color = 'white'
sw1.x = ((WIDTH - sw1_width) / 2)
sw1.y = ((HEIGHT * sw1_y / 100))

bt1 = ui.Button(name='Button_get_user_id', bg_color='#3664a8')
bt1.border_color = '#808080'
bt1.tint_color = 'white'
bt1.border_width = 5
bt1.corner_radius = 50
bt1.width = bt1_width
bt1.height = bt1_height
bt1.alignment = 1
bt1.title = 'Benutzercode Abfrage Starten'
bt1.x = ((WIDTH - bt1.width) / 2)
bt1.y = ((HEIGHT * bt1_y / 100))
bt1.font = (get_font(), bt1_font_size)


view.present('fullscreen')


view.add_subview(lb1)
view.add_subview(tv1)
view.add_subview(sc1)
view.add_subview(lb1_1)
view.add_subview(sw1)
view.add_subview(bt1)

or is there no better way to solve that ?

JonB
SCREEN = ui.get_screen_size()
if min(SCREEN) >= 768:
    # iPad
    WIDTH = ''
    for i in SCREEN:
        if not WIDTH:
            WIDTH = i
        else:
            HEIGHT = i
else:
    # iPhone
    WIDTH = ''
    for i in SCREEN:
        if not WIDTH:
            WIDTH = i
        else:
            HEIGHT = 

Could be written simply as WIDTH, HEIGHT = ui.get_screen_size(). Python let's you split up tuples easily that way

There are some methods using flex to create one size view that will resize automatically, but it can be hard to get right, plus you still have to manually set font sizes, etc.

@mikael has some code for writing better constraints, which can make it easier to write views that adapt to screen sizes. His grid view class also makes it easy to build very simple ui's..

DavinE

i will have a look at this

ty

mikael

@DavinE, the more recent and better version of UI constraints is here.

But even without going there, as @JonB said, you can get quite a lot done with just a GridView, which is now bundled with uiutils (pip install pythonista-uiutils).

The following short example demostrates 3 things that may be of interest if you are trying to create flexible UIs targeting both iPads and iPhones, and want your UI to survive someone rotating the device:

  • Define an area to fill a percentage of the screen (plain ui flex).
  • Avoid the controls at the edges of phone displays by using the SafeAreaView.
  • Use GridView to layout views (buttons) automatically.
import ui

from uiutils.safearea import SafeAreaView
from uiutils.gridview import GridView

def create_button_slot(title):
    slot = ui.View(
        background_color='darkgrey',
    )
    btn = ui.Button(
        title=title,
        background_color='white',
        tint_color='black',
        flex='TWB'
    )
    btn.size_to_fit()
    btn.height = btn.height + 16
    btn.width = slot.width
    btn.corner_radius = btn.height/2
    btn.center = slot.bounds.center()
    slot.add_subview(btn)
    return slot

root = SafeAreaView(
    background_color='lightgrey'
)

button_area_percentage = 23

button_area = GridView(
    pack=GridView.FILL,
    background_color='grey',
    frame=(0, 100-button_area_percentage, 100, button_area_percentage),
    flex='TWH',
)

root.add_subview(button_area)

for i in range(6):
    button_area.add_subview(create_button_slot(f'Button {i+1}'))

root.present("fullscreen", animated=False)
DavinE

@mikael thanks for the example

next week i will try this to get it :D

DavinE

@mikael said:

@DavinE, the more recent and better version of UI constraints is here.

But even without going there, as @JonB said, you can get quite a lot done with just a GridView, which is now bundled with uiutils (pip install pythonista-uiutils).

The following short example demostrates 3 things that may be of interest if you are trying to create flexible UIs targeting both iPads and iPhones, and want your UI to survive someone rotating the device:

  • Define an area to fill a percentage of the screen (plain ui flex).
  • Avoid the controls at the edges of phone displays by using the SafeAreaView.
  • Use GridView to layout views (buttons) automatically.

```
import ui

from uiutils.safearea import SafeAreaView
from uiutils.gridview import GridView

def create_button_slot(title):
slot = ui.View(
background_color='darkgrey',
)
btn = ui.Button(
title=title,
background_color='white',
tint_color='black',
flex='TWB'
)
btn.size_to_fit()
btn.height = btn.height + 16
btn.width = slot.width
btn.corner_radius = btn.height/2
btn.center = slot.bounds.center()
slot.add_subview(btn)
return slot

root = SafeAreaView(
background_color='lightgrey'
)

button_area_percentage = 23

button_area = GridView(
pack=GridView.FILL,
background_color='grey',
frame=(0, 100-button_area_percentage, 100, button_area_percentage),
flex='TWH',
)

root.add_subview(button_area)

for i in range(6):
button_area.add_subview(create_button_slot(f'Button {i+1}'))

root.present("fullscreen", animated=False)
```

i have a Problem with the UI constraints..
i Installed anchors but i get the message that the Module named 'anchor' is not found...

EDIT: i found the solution:

observer.py
this code is wrong:

import anchor.objc_plus as objc_plus

this is right:
import anchors.objc_plus as objc_plus

mikael

@DavinE, thanks! I pushed an update to PyPI.

GxAl2aa

@DavinE said:

Hello Guys,

I have an Problem with my view on different devices...
i used fix position with calculator to get it better but it looks not really good...

how can i fix it to do it better look or how is it correct to script that..
i have no idea.... i hope you guys can help me..

in my Pic you see two different iPad devices... and maybe its better to understand ;)

center
too far left

DavinE

@mikael no Problem ;)

i Have an Question about the UI constraints..

When i use Buttons with corner radius = 50 my text is cutted.....
i tried to fix this with the Width... but it didn't worked.....

JonB

you could probably change horizontal alignment to center. i think the right way to do it would be to adjust the

contentEdgeInsets

objc attribute of the underlying uibutton, to allow at least the edge radius amount of inset in left and right sides (or maybe edge radius divided by sqrt(2) on all 4 sides).

DavinE

@JonB thanks for the fast reply but i understand nothing.. :(

do you have an example for me where i can see what your mean ?

an other Thing what i don't understand is when i use for example the dock element top_center at 2 Buttons its overlapping

how can i fix this.....

thanks for your help!

mikael

@DavinE, can you share a small example of your problem case?

JonB

i think this shows the problem, and a potential solution:

import ui
from objc_util import *

v=ui.View(bg_color='white')

b=ui.Button(title='press to resize with insets')

b.border_width=1
b.corner_radius=50

@on_main_thread
def a(sender):
    sender.objc_instance.button().contentEdgeInsets=(0, 50,0, 50) #t,l,b,r
    sender.size_to_fit()

b.action=a

v.add_subview(b)
v.present()

The height changes when using this method, so i assume that those edgeinsets are overriding some other sort of anchor or layout constraint that i don't get.

mikael

@JonB, sorry to be thick, but I still do not understand what the problem is or what the insets are for. In the example above I used corner radius to create buttons with rounded ends, but this must be about something else.

mikael

@DavinE, if you use dock top_center on two buttons, their top centers will be placed in the same place and they will overlap.

If you want them one above the other, you can still dock both top_center, and then adjust the second view as follows:

at(second).top = at(first).bottom
DavinE

i'll Show you tomorrow or on the Weekend my Examples for my two Problems

DavinE

@mikael said:

@DavinE, if you use dock top_center on two buttons, their centers will be placed in the same place and they will overlap.

If you want them one above the other, you can dock both top_center, and then adjust the second view as follows:

at(second).top = at(first).bottom

Yes Thats My issue...
They will overlap....

I will have a Look at this in the Weekend.

Short and stupid Question:
First second are my two Buttons ??

Thanks a looooot for the great help here!!

mikael

@DavinE said:

First second are my two Buttons ??

Yes, just example names.

DavinE

@JonB said:

i think this shows the problem, and a potential solution:
```
import ui
from objc_util import *

v=ui.View(bg_color='white')

b=ui.Button(title='press to resize with insets')

b.border_width=1
b.corner_radius=50

@on_main_thread
def a(sender):
sender.objc_instance.button().contentEdgeInsets=(0, 50,0, 50) #t,l,b,r
sender.size_to_fit()

b.action=a

v.add_subview(b)
v.present()

```

The height changes when using this method, so i assume that those edgeinsets are overriding some other sort of anchor or layout constraint that i don't get.

thats a good example...
but is it Possible to set the size at begin ?
not over an action....

is this example okay to do this so ?:

import ui
from objc_util import *

v=ui.View(bg_color='white')

b=ui.Button(title='press to resize with insets')

b.border_width=5
b.corner_radius=50
b.objc_instance.button().contentEdgeInsets=(5, 50,5, 50)
b.size_to_fit()

v.add_subview(b)
v.present()
DavinE

@mikael said:

@DavinE said:

First second are my two Buttons ??

Yes, just example names.

Thanks this works Perfect :D

DavinE

@mikael said:

@JonB, sorry to be thick, but I still do not understand what the problem is or what the insets are for. In the example above I used corner radius to create buttons with rounded ends, but this must be about something else.

My Problem is when i use border_width and border_radius my text is cutted by the Button...
@JonB understand my Problem correctly

mikael

@DavinE, inspired by your question, I uploaded a new version with an attach option with these functions:
- above
- below
- left_of
- right_of

With those, your use case could also be:

dock(first).top_center(superview)
attach(second).below(first)
DavinE

@mikael said:

@DavinE, inspired by your question, I uploaded a new version with an attach option with these functions:
- above
- below
- left_of
- right_of

With those, your use case could also be:

dock(superview).top_center(first)
attach(second).below(first)

Great :D how can i Update my files ?
over stash ?

mikael

@DavinE, pip update pythonista-anchors

DavinE

oh man..... i'm so stupid :D

ty @mikael

mikael

@DavinE, don’t be hard on yourself. Just remember you have the -h option for most stash commands, like pip -h.

DavinE

Thanks @mikael for that great Support here Thumbs Up

DavinE

@mikael said:

@DavinE, inspired by your question, I uploaded a new version with an attach option with these functions:
- above
- below
- left_of
- right_of

With those, your use case could also be:

dock(first).top_center(superview)
attach(second).below(first)

Hay @mikael,

i Think i found an issue in the attach function:

xxx.../site-packages-3/anchors/core.py:780: ConstraintWarning: Probably missing superview
  at(self.view).left = at(other).right
xxx.../site-packages-3/anchors/core.py:801: ConstraintWarning: Probably missing superview
  getattr(anchor_at, prop) + modifier)
Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 234, in 'calling callback function'
  File "xxx..../Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 1066, in OMMainThreadDispatcher_invoke_imp
    retval = func(*args, **kwargs)
  File "xxx.../site-packages-3/anchors/core.py", line 213, in on_change
    value_changed = next(constraint.runner)
  File "<string>", line 27, in constraint_runner
AttributeError: 'NoneType' object has no attribute 'bounds'

i get this by:

attach(second).right_of(first)
mikael

@DavinE, yes thank you, there was a missing add_subview, now fixed in the version on PyPI.

DavinE

@mikael said:

@DavinE, yes thank you, there was a missing add_subview, now fixed in the version on PyPI.

Great :D