Forum Archive

IPhone voice over.

shinya.ta

I made a keyboard input auxiliary application for cvp for my wife's wife who is visually impaired.

I used to go well at the beginning, but after I updated my iPhone, I started to use a word different from the word after I had the voice over.

I don't know whether it is the cause of Pythonisa or iOS.

The program which the following is made.

import ui
from objc_util import *
import clipboard
import speech

v = ui.View()
v.frame = (0,0,500,320)
v.name = 'Move cursor in TextView'

tv = ui.TextView()
tv.name = 'TextView'
tv.frame = (120,10,370,300)
tv.font = ('Arial Rounded MT Bold',24)
tv.text = 'aรฉ๐Ÿ˜ข๐Ÿ‡ฏ๐Ÿ‡ต๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฉโ€๐ŸŽจ'
v.add_subview(tv)

def say_char(tv):
    # test speech character at cursor
    idxtopos = IndexToPos('') # list index to position
    i = tv.selected_range[0]
    #print(i,idxtopos)
    i = idxtopos[i] # used to check if same base character
    if i < len(tv.text):
        c = tv.text[i]
        if c == ' ':
            c ='space'
        speech.say(c,'jp-JP')

def selected_range(i):
    tvo = ObjCInstance(tv)
    p1 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), i)
    p2 = p1
    #p2 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), i+1)
    tvo.selectedTextRange = tvo.textRangeFromPosition_toPosition_(p1, p2)
    say_char(tv)
    return

# some emoji like flags count as 2 for len but as 4 for selected_range
def IndexToPos(type):
    tvo = ObjCInstance(tv)
    # build array index -> position in range
    idxtopos = []
    pre_x = -1
    #print(tv.text)
    i = 0
    for c in tv.text:
      # nbr characters used e=1 รฉ=1 ๐Ÿ˜‚=1 ๐Ÿ‡ฏ๐Ÿ‡ต=2 ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง=7
      # some emoji generate more than one character, 
      # sometimes counted for more than one in range
      # 1,2,3->1  4->2
      nb = 1 + int(len(c.encode('utf-8'))/4)
      for j in range(0,nb):
        p1 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), len(idxtopos))
        p2 = p1
        rge = tvo.textRangeFromPosition_toPosition_(p1, p2)
        rect = tvo.firstRectForRange_(rge)  # CGRect
        x = rect.origin.x
        if x == float('inf') or x == pre_x:
          # same x as previous one, composed character
          pass
        else:
          pre_x = x
          i = len(idxtopos)
        idxtopos.append(i)                      # start position of c
      #print(c,nb,len(idxtopos)-1,i,x)
    idxtopos.append(i+1)                            # end position of last c
    #print(idxtopos)
    # get index of actual cursor
    i = tv.selected_range[0]                # actual position of cursor
    # often p is one of sub_chars, not always the first one
    p = idxtopos[i]                                 # used to check if same base character
    #print(p,i,idxtopos)
    if type == 'left':
      if i == 0:
        return                                              # already before character
      while True:
        i = i - 1
        if idxtopos[i] != p:
          q = idxtopos[i]
          # seach first sub-character
          while i > 0:
            if idxtopos[i-1] != q:
              break
            i = i - 1
        break
    elif type == 'right':
      if i == (len(idxtopos)-1):
        return                                              # already after last character
      while True:
        i = i + 1
        if idxtopos[i] != p:
          break
    elif type == 'end':
      i = len(idxtopos)-1
    else:
      return idxtopos
    r = idxtopos[i]
    selected_range(i)
    return idxtopos

b_top = ui.Button()
b_top.frame = (10,10,100,32)
b_top.title = 'begin'
b_top.background_color = 'white'
b_top.border_width = 1
def b_top_action(sender):
    tv = sender.superview['TextView']
    tv.selected_range = (0,0)
    say_char(tv)
b_top.action = b_top_action
v.add_subview(b_top)

b_left = ui.Button()
b_left.frame = (10,50,100,32)
b_left.title = 'left'
b_left.background_color = 'white'
b_left.border_width = 1
def b_left_action(sender):
    idxtopos = IndexToPos('left')       # list index to position
b_left.action = b_left_action
v.add_subview(b_left)

b_right = ui.Button()
b_right.frame = (10,90,100,32)
b_right.title = 'right'
b_right.background_color = 'white'
b_right.border_width = 1
def b_right_action(sender):
    idxtopos = IndexToPos('right')      # list index to position
b_right.action = b_right_action
v.add_subview(b_right)

b_bottom = ui.Button()
b_bottom.frame = (10,130,100,32)
b_bottom.title = 'end'
b_bottom.background_color = 'white'
b_bottom.border_width = 1
def b_bottom_action(sender):
    idxtopos = IndexToPos('end')        # list index to position
b_bottom.action = b_bottom_action
v.add_subview(b_bottom)

def get_xy(tv):
    idxtopos = IndexToPos('')       # list index to position
    tvo = ObjCInstance(tv)
    x_y = []
    for i in range(0,len(idxtopos)+1):  # x,y of each character
      p1 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), i)
      rge = tvo.textRangeFromPosition_toPosition_(p1,p1)
      rect = tvo.firstRectForRange_(rge)    # CGRect
      x,y = rect.origin.x,rect.origin.y
      if i == len(idxtopos):
        if i > 0:
          x,y = x_y[i-1]
        else:
          # text is empty
          x,y = 0,0
      if x == float('inf'):
        x,y = x_prec+15,y_prec
      x_prec,y_prec = x,y
      x_y.append((x,y))
    return x_y

b_up = ui.Button()
b_up.frame = (10,170,100,32)
b_up.title = 'up'
b_up.background_color = 'white'
b_up.border_width = 1
def b_up_action(sender):
    tv = sender.superview['TextView']
    x_y = get_xy(tv)
    c = tv.selected_range[0] 
    xc,yc = x_y[c]
    i = c - 1
    while i >=  0:
      x,y = x_y[i]
      if y < yc:
        # previous row
        if x <= xc:
          selected_range(i)
          return
      i = i - 1
b_up.action = b_up_action
v.add_subview(b_up)

b_down = ui.Button()
b_down.frame = (10,210,100,32)
b_down.title = 'down'
b_down.background_color = 'white'
b_down.border_width = 1
def b_down_action(sender):
    tv = sender.superview['TextView']
    idxtopos = IndexToPos('')       # list index to position
    x_y = get_xy(tv)
    c = tv.selected_range[0] 
    #print(x_y,c)
    xc,yc = x_y[c]
    i = c# - 1          # I don't remember why this "- 1"
    while i < len(idxtopos):
      x,y = x_y[i]
      if y > yc:
        # next row
        if x >= xc:
          selected_range(i)
          return
        else:
          if (i+1) < len(idxtopos):
            if x_y[i+1][1] > y: # i = last character of row under cursor
              selected_range(i)
              return
            else:
              pass  # try next x
          else:
            # last character of last row
            selected_range(i)
            return
      i = i + 1
b_down.action = b_down_action
v.add_subview(b_down)

b_copy = ui.Button()
b_copy.frame = (10,250,100,32)
b_copy.title = 'copy'
b_copy.background_color = 'white'
b_copy.border_width = 1
def b_copy_action(sender):
    tv = sender.superview['TextView']
    clipboard.set(tv.text)
b_copy.action = b_copy_action
v.add_subview(b_copy)

b_clear = ui.Button()
b_clear.frame = (10,290,100,32)
b_clear.title = 'clear'
b_clear.background_color = 'white'
b_clear.border_width = 1
def b_clear_action(sender):
    tv = sender.superview['TextView']
    tv.text = ''
b_clear.action = b_clear_action
v.add_subview(b_clear)

def typeChar(sender):
    '''finds active textinput, and types the button's title'''
    tf=sender.objc_instance.firstResponder()
    tf.insertText_(sender.title)

#create special keys
def prev(sender):
    '''simulates 'tab' key, go to next field '''

    s=sender.objc_instance.firstResponder()._previousKeyResponder().becomeFirstResponder()
    buttons.append(ui.Button(image=ui.Image.named('iob:ios7_arrow_back_32'),action=prev))

def next(sender):
    '''simulates 'tab' key, go to next field '''
    s=sender.objc_instance.firstResponder()._nextKeyResponder().becomeFirstResponder()
    buttons.append(ui.Button(image=ui.Image.named('iob:ios7_arrow_forward_32'),action=next))


#create normal keys
d = 32
dd = 4
emojis = '๐Ÿ˜Š๐Ÿ˜œ๐Ÿ˜ฑ๐Ÿ’ฆโ˜”๏ธ๐Ÿ˜€๐Ÿ˜ƒ๐Ÿ˜„๐Ÿ˜๐Ÿ˜†๐Ÿ˜…๐Ÿ˜‚๐Ÿคฃโ˜บ๏ธ๐Ÿ˜Š๐Ÿ˜‡๐Ÿ™‚๐Ÿ™ƒ๐Ÿ˜‰๐Ÿ˜Œ๐Ÿ˜๐Ÿฅฐ๐Ÿ˜˜๐Ÿ˜—๐Ÿ˜™๐Ÿ˜š๐Ÿ˜‹๐Ÿ˜›๐Ÿ˜๐Ÿ˜œ๐Ÿคช๐Ÿคจ๐Ÿง๐Ÿค“๐Ÿ˜Ž๐Ÿคฉ๐Ÿฅณ๐Ÿ˜๐Ÿ˜’๐Ÿ˜ž๐Ÿ˜”๐Ÿ˜Ÿ๐Ÿ˜•๐Ÿ™โ˜น๏ธ๐Ÿ˜ฃ๐Ÿ˜–๐Ÿ˜ซ๐Ÿ˜ฉ๐Ÿฅบ๐Ÿ˜ข๐Ÿ˜ญ๐Ÿ˜ค๐Ÿ˜ ๐Ÿ˜ก๐Ÿคฌ๐Ÿคฏ๐Ÿ˜ณ๐Ÿฅต๐Ÿฅถ๐Ÿ˜ฑ๐Ÿ˜จ๐Ÿ˜ฐ๐Ÿ˜ฅ๐Ÿ˜“๐Ÿค—๐Ÿค”๐Ÿคญ๐Ÿคซ๐Ÿคฅ๐Ÿ˜ถ๐Ÿ˜๐Ÿ˜‘๐Ÿ˜ฌ๐Ÿ˜ฆ๐Ÿ˜ง๐Ÿ˜ฎ๐Ÿ˜ฒ๐Ÿ˜ด๐Ÿคค๐Ÿ˜ช๐Ÿ˜ต๐Ÿค๐Ÿฅด๐Ÿคข๐Ÿคฎ๐Ÿคง๐Ÿ˜ท๐Ÿค’๐Ÿค•๐Ÿค‘๐Ÿค ๐Ÿ˜ˆ'
n_emojis_in_set = 20
n_sets = 1 + int((len(emojis)-1)/n_emojis_in_set)

tv.i_set = 0
tv.n_sets = n_sets
def nextSet(sender):
    tv.i_set = tv.i_set + 1
    if tv.i_set == tv.n_sets:
        tv.i_set = 0
    #attach our accessory to the textfield, and textview
    ww = vv_array[tv.i_set]
    tvo = tv.objc_instance
    #print(dir(tvo))
    tvo.setInputAccessoryView_(ObjCInstance(ww))
    tvo.reloadInputViews()

vv_array = []
for i_set in range(0,n_sets):
    l = int(len(emojis)/n_sets)
    i = i_set * l
    set_emojis = emojis[i:i+l]
    w, h = ui.get_screen_size() 
    vv = ui.View(name='set'+str(i_set))
    vv.background_color = 'lightgray'
    h = 0
    x = dd
    y = dd
    for button_title in set_emojis:
        b = ui.Button(title=button_title)
        b_action = typeChar
        b.action=b_action
        b.frame = (x,y,d,d)
        b.font = ('.SFUIText', d)
        if (y+d+dd) > h:
            h = y + d + dd
        vv.add_subview(b)
        x = x + d + dd
        if (x+d+dd) > w:
            x = dd
            y = y + d + dd
    device = ObjCClass('UIDevice').currentDevice().model()
    if str(device) == 'iPhone':
        bb_target = ui.Button()
        bb_target.title = 'next emojis'
        wb,hb = ui.measure_string(bb_target.title,font=bb_target.font)
        bb_target.action = nextSet
        bb_target.frame = (dd,h,wb+2,d)
        vv.add_subview(bb_target)
        h = h + d + dd
    vv.frame = (0,0,w,h)
    vv_array.append(vv)

nextSet(vv_array[n_sets-1]['nextSet'])  # display 1st set

device = ObjCClass('UIDevice').currentDevice().model()
if str(device) == 'iPad':
    # add a button at right of "typing suggestions", just above the keyboard
    bb_target = ui.Button()
    bb_target.action = nextSet

    UIBarButtonItem = ObjCClass('UIBarButtonItem').alloc().initWithTitle_style_target_action_('next emojis',0,bb_target,sel('invokeAction:')).autorelease()
    #UIBarButtonItem = ObjCClass('UIBarButtonItem').alloc().initWithImage_style_target_action_(ns(ui.Image.named('emj:Bicycle').with_rendering_mode(ui.RENDERING_MODE_ORIGINAL)),0,bb_target,sel('invokeAction:')).autorelease()

    UIBarButtonItemGroup = ObjCClass('UIBarButtonItemGroup').alloc().initWithBarButtonItems_representativeItem_([UIBarButtonItem],None)

    tvo = tv.objc_instance
    #tvo.inputAssistantItem().setTrailingBarButtonGroups([UIBarButtonItemGroup])
    tvo.inputAssistantItem().setLeadingBarButtonGroups([UIBarButtonItemGroup])
    #print(dir(tvo))
    #print(dir(tvo.inputAssistantItem()))

v.present('sheet')
tv.selected_range = (0,0)
tv.begin_editing()
cvp

@shinya.ta said:

I started to use a word different from the word after I had the voice over.

I don't understand this explanation..

And what do you mind with "after I updated my iPhone?"

shinya.ta

@cvp

Sorry for the poor English.

My native language is Japanese.

It is input in alphabets, then converted into Kanji.
There are many prediction words in the conversion list.
Among those, we choose the most suitable word and decide.
When you use voice over, they will read the detailed explanation of the word.
But after the detailed explanation, I'll make a strange pronunciation that has nothing to do with the word.
I don't know what other languages are, because I don't use other languages.

shinya.ta

@cvp

When strange words are read aloud, I mistakenly misunderstand that my wife entered wrong words.

cvp

@shinya.ta Really, I don't know from where the problem comes.
You said that happens since your iPhone update. Is that an IOS update?
If yes, this could be an iOS bug...

shinya.ta

@cvp

It's since I updated iOS.

Is it impossible after all?

cvp

@shinya.ta Sorry, no idea, I don't know anything about Voice Over