Forum Archive

Real numeric pad on iPad

cvp

A small code to allow (the daughter of) @estephan500 and @runjaj to use a real numeric pad on iPad, with your own
- cursor color
- number of rows
- icons for backspace and enter/return/done

In your site-packages folder, create SetTextFieldAsNumeric.py with

import ui
from objc_util import *

def SetTextFieldAsNumeric(tf):
    tfo = ObjCInstance(tf).textField() # UITextField is subview of ui.TextField
    #print(dir(tfo))

    def key_pressed(sender):

            tfb = sender.TextField
            tfobjc = ObjCInstance(tfb).textField()
            cursor = tfobjc.offsetFromPosition_toPosition_(tfobjc.beginningOfDocument(), tfobjc.selectedTextRange().start())
            if sender.name == 'x':
                if cursor > 0:
                    #if tfb.text != '':
                    tfb.text = tfb.text[:cursor-1] + tfb.text[cursor:]
                    cursor = cursor - 1
            elif sender.name == 'y':
                tfb.end_editing()
                return
            else:
                tfb.text = tfb.text[:cursor] + sender.title + tfb.text[cursor:]
                cursor = cursor + 1

            # set cursor
            cursor_position = tfobjc.positionFromPosition_offset_(tfobjc.beginningOfDocument(), cursor)
            tfobjc.selectedTextRange = tfobjc.textRangeFromPosition_toPosition_(cursor_position, cursor_position)

    # design your keyboard
    #           | = new row
    #           x = back space => left delete
    #           y = done => discard the keyboard    
    keys = '123x|456|789y| 0'
    #keys = '1234567890x y'
    rows = keys.split('|')
    row_length = 0
    for row in rows:
        if len(row) > row_length:
            row_length = len(row)

    v = ui.View()
    db = 50
    dd = 10
    x0 = (ui.get_screen_size()[0]-row_length*db-(row_length-1)*dd)/2
    x = x0
    y = 0

    for row in rows:
        for c in row:
            if c != ' ':
                b = ui.Button()
                b.name = c
                b.background_color = 'white'    # or any other color
                b.tint_color = 'black'
                b.corner_radius = 10 
                if c == 'x':
                    b.image = ui.Image.named('typb:Delete')
                elif c == 'y':
                    b.title = ''
                    b.image = ui.Image.named('emj:Checkmark_3').with_rendering_mode(ui.RENDERING_MODE_ORIGINAL)
                    #b.image = ui.Image.named('iob:ios7_checkmark_outline_32')
                else:
                    b.title = c
                b.font = ('<system>',32)
                b.frame = (x,y,db,db)
                b.TextField = tf # store tf as key attribute  needed when pressed
                b.action = key_pressed
                v.add_subview(b)
            x = x + db + dd
        y = y + db + dd
        x = x0

    v.width = ui.get_screen_size()[0]
    v.height = y

    # view of keyboard
    retain_global(v) # see https://forum.omz-software.com/topic/4653/button-action-not-called-when-view-is-added-to-native-view
    tfo.setInputView_(ObjCInstance(v))

    # color of cursor and selected text
    tfo.tintColor = UIColor.redColor().colorWithAlphaComponent(0.5)

You can use it like this:

import ui
from SetTextFieldAsNumeric import SetTextFieldAsNumeric

tf = ui.TextField()
SetTextFieldAsNumeric(tf)
tf.text = ''
tf.width = 400
tf.present('sheet')
tf.begin_editing()
tf.wait_modal()


or

cvp

Add these lines at end of SetTextFieldAsNumeric.py if you want to remove standard undo/redo/paste BarButtons above keyboard

    # comment both lines to keep undo/redo/paste BarButtons above keyboard
    tfo.inputAssistantItem().setLeadingBarButtonGroups(None)
    tfo.inputAssistantItem().setTrailingBarButtonGroups(None)

DaveClark

I write calculation type apps for my work. I like the simplicity of your number keyboard. It is straight forward and not a lot of noise. I would like to use it in my stuff on my iphone and ipad. How would I modify it to add a decimal point?

any help would be very much appreciated

cvp

@DaveClark change only one line

    keys = '123x|456|789y| 0.'

If SetTextFieldAsNumeric.py resides in site-packages, you will have to restart Pythonista.

The decimal point key is appended like the digits but no check is done, for instance if you type 12..34 instead of 12.34

JonB

@cvp you might consider creating named "virtual keys" encoded using brackets, instead of using x and y, say [cancel] and [done] or something like that. Or use some sort of Unicode character. I could see people wanting to use this for custom keyboards that include full or abbreviated alphabets. Also, I could agine applications where other custom keys might be desirble, such as arrow keys, "calculator" popups...
You might have a named custom key dict which lets you define in one place the icon or title, and a function of the to call to do the action.
Again, this was a great idea and was well executed.

cvp

@JonB I agree with you, we could generalize the function with more parameters but I only have tried something by reading Apple inputview. And I'm not a specialist of args 😢.
I just want to be sure readers can easily understand my short code and understand how they could add their own keys with their own icons and actions...

And thousand thanks for your last line, it makes me more happy even than the last match of Belgium (my country) in RussiašŸ˜‚

DaveClark

@cvp Thank you for your help and quick answer on adding the decimal point to your keypad

cvp

@JonB like this SetTextFieldPad.py

import ui
from objc_util import *

def SetTextFieldPad(tf,pad=None):
    if not pad:
        pad = [{'key':'1'},{'key':'2'},{'key':'3'},
        {'key':'back space','icon':'typb:Delete'},
        {'key':'new row'},
        {'key':'4'},{'key':'5'},{'key':'6'},
        {'key':'delete','icon':'emj:Multiplication_X'},
        {'key':'new row'},
        {'key':'7'},{'key':'8'},{'key':'9'},
        {'key':'done','icon':'emj:Checkmark_3'},
        {'key':'new row'},
        {'key':'nul'},{'key':'0'}]
    tfo = ObjCInstance(tf).textField() # UITextField is subview of ui.TextField

    def key_pressed(sender):

            tfb = sender.TextField
            tfobjc = ObjCInstance(tfb).textField()
            cursor = tfobjc.offsetFromPosition_toPosition_(tfobjc.beginningOfDocument(), tfobjc.selectedTextRange().start())
            if sender.name == 'delete':
                if cursor <= (len(tfb.text)-1):
                    tfb.text = tfb.text[:cursor] + tfb.text[cursor+1:]
            elif sender.name == 'back space':
                if cursor > 0:
                    #if tfb.text != '':
                    tfb.text = tfb.text[:cursor-1] + tfb.text[cursor:]
                    cursor = cursor - 1
            elif sender.name == 'done':
                tfb.end_editing()
                return
            else:
                tfb.text = tfb.text[:cursor] + sender.title + tfb.text[cursor:]
                cursor = cursor + 1

            # set cursor
            cursor_position = tfobjc.positionFromPosition_offset_(tfobjc.beginningOfDocument(), cursor)
            tfobjc.selectedTextRange = tfobjc.textRangeFromPosition_toPosition_(cursor_position, cursor_position)

    # design your keyboard
    # pad = [{key='functionnality',title='title',icon='icon'},...]
    #       new row => new row
    #       nul => no key
    #       back space => left delete
    #       delete => right delete
    #       done => discard the keyboard
    #   other => append the character

    # count the maximum width of rows
    row_max_length = 0
    row_length = 0
    for pad_elem in pad:
        if pad_elem['key'] == 'new row':
            if row_length > row_max_length:
                row_max_length = row_length
            row_length = 0      
        else:
            row_length = row_length + 1
    if row_length > row_max_length:
        row_max_length = row_length

    v = ui.View()
    db = 50
    dd = 10
    x0 = (ui.get_screen_size()[0]-row_max_length*db-(row_max_length-1)*dd)/2
    x = x0
    y = dd

    for pad_elem in pad:
        if pad_elem['key'] == 'new row':
            y = y + db + dd
            x = x0
        elif pad_elem['key'] == 'nul':          
            x = x + db + dd
        else:           
            b = ui.Button()
            b.name = pad_elem['key']
            b.background_color = 'white'    # or any other color
            b.tint_color = 'black'
            b.corner_radius = 10 
            b.title = ''
            b.font = ('<system>',32)
            if 'icon' in pad_elem:
                b.image = ui.Image.named(pad_elem['icon']).with_rendering_mode(ui.RENDERING_MODE_ORIGINAL)
            elif 'title' not in pad_elem:
                b.title = pad_elem['key']
            if 'title' in pad_elem:
                b.title = pad_elem['title']

            b.frame = (x,y,db,db)
            b.TextField = tf # store tf as key attribute  needed when pressed
            if 'action' in pad_elem:
                b.action = pad_elem['action']
            else:
                b.action = key_pressed
            v.add_subview(b)
            x = x + db + dd
    y = y + db + dd

    v.width = ui.get_screen_size()[0]
    v.height = y

    # view of keyboard
    retain_global(v) # see https://forum.omz-software.com/topic/4653/button-action-not-called-when-view-is-added-to-native-view
    tfo.setInputView_(ObjCInstance(v))

    # color of cursor and selected text
    tfo.tintColor = UIColor.redColor().colorWithAlphaComponent(0.5)

    # comment both lines to keep undo/redo/paste BarButtons above keyboard
    tfo.inputAssistantItem().setLeadingBarButtonGroups(None)
    tfo.inputAssistantItem().setTrailingBarButtonGroups(None)

And

import ui
from SetTextFieldPad import SetTextFieldPad

def my_action(sender):
    sender.TextField.name = 'my_action called'
    sender.TextField.text = 'auto_filled by my_action'

tf = ui.TextField()
#pad = [{'key':'','title'=:'','icon':''}]
pad = [{'key':'1'},{'key':'2'},{'key':'3'},
        {'key':'back space','icon':'typb:Delete'},
        {'key':'new row'},
        {'key':'4'},{'key':'5'},{'key':'6'},
        {'key':'delete','icon':'emj:Multiplication_X'},
        {'key':'new row'},
        {'key':'7'},{'key':'8'},{'key':'9'},
        {'key':'ole','action':my_action,'icon':'typb:User'},
        {'key':'done','icon':'emj:Checkmark_3'},
        {'key':'new row'},
        {'key':'nul'},{'key':'0'}]
SetTextFieldPad(tf,pad=pad)
tf.text = ''
tf.width = 400
tf.present('sheet')
tf.begin_editing()
tf.wait_modal()

JonB

exactly, nice

cvp

@JonB Thanks. Did you see I ā€˜ve added the functionality ā€œdelete at (right of) cursorā€.

DaveClark

I have something that works on a iPad, but I would like to incorporate the new number pad in the program. This is where I’m at

import ui

def getInput(view):
    textfield = v[view]
    input = textfield.text
    return input

def calculate(top, bottom): 
    result = repr(round(1/(1/top + 1/bottom))) + ' pounds per inch'   
    return result

def button_tapped(sender):
    top = float(getInput('textfield1'))
    bottom = float(getInput('textfield2'))
    v['textfield3'].text = calculate(top, bottom) 

v = ui.load_view('uitest')

v['textfield1'].keyboard_type = ui.KEYBOARD_DECIMAL_PAD   
v['textfield2'].keyboard_type = ui.KEYBOARD_DECIMAL_PAD  

button1 = v['button1']
button1.action = button_tapped
v.present('sheet')

How would I make your keypad be the default keypad.

Any help would be very much appreciated

cvp

@DaveClark Sorry for the delay but I'm in holidays far from my home.
Try
```
import ui
from SetTextFieldPad import SetTextFieldPad

import ui

def getInput(view):
textfield = v[view]
input = textfield.text
return input

def calculate(top, bottom):
result = repr(round(1/(1/top + 1/bottom))) + ' pounds per inch'
return result

def button_tapped(sender):
top = float(getInput('textfield1'))
bottom = float(getInput('textfield2'))
v['textfield3'].text = calculate(top, bottom)

v = ui.load_view('uitest')

v['textfield1'].keyboard_type = ui.KEYBOARD_DECIMAL_PAD

v['textfield2'].keyboard_type = ui.KEYBOARD_DECIMAL_PAD

SetTextFieldPad(v['textfield1'])
SetTextFieldPad(v['textfield2'])

button1 = v['button1']
button1.action = button_tapped
v.present('sheet')```

DaveClark

@cvp Oh my God. That’s it, You should see the mess I have got going in trying to incorporate your keypad. I guess I don’t get it yet, but I’m trying.

Thank you so much for your help, and thank you for taking the time to help. I really enjoy the little programming projects I have and I am getting there

Thanks again

cvp

Added to my SetTextFieldPad support of SFSymbols as icon on keyboard key