Forum Archive

Ui.TextView, detect if virtual keyboard is open/visible

rownn

Hi everyone,

I work on a scene with ui.TextView and Iโ€˜d like to know how I could detect if the virtual keyboard is open/visible or not. Depending on the visibility of the keyboard Iโ€˜d like to do some layout changes. Does somebody have a hint for me?

Greetings :)
rownn

stephen

@rownn i have an old script that handles what you are wanting. while i search for it look at the TextView Delegate here

cvp

@rownn test this

from objc_util import *
import ui

tv =ui.TextView()

def x(sender):
    resp = ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder()
    tv.name = 'keyboard is visible = ' + str(resp == ObjCInstance(tv))

b = ui.ButtonItem()
b.title = 'test'
b.action = x
tv.right_button_items = (b,)
tv.present() 
cvp

@rownn if you have multiple TextFields/ TextViews you could compare the first responder with ObjCInstance(all of them) to determine which one has the keyboard/cursor

stephen

@rownn i annotated with comments.


class MyTextView(ui.TextView):
    def __init__(self, *args, **kwargs):
        self.delegate = self

    def begin_editing(self):
        # forces keyboard to present
        pass

    def end_editing(self):
        # forces keyboard to close
        pass

    def replace_range(self, range, text):
        pass

    ### start of delegate methods ###

    def TextView_should_begin_editing(self, textview):
        # resize and reposition for keyboard
        return True

    def TextView_did_begin_editing(self, textview):
        # keyboard presented
        pass

    def TextView_did_end_editing(self, textview):
        pass

    def TextView_should_return(self, textview):
        # resize and reposition for no keyboard
        textfield.end_editing()
        # keyboard not presented
        return True

    def TextView_should_change(self, textview, range, replacement):
        return True

    def TextView_did_change(self, textview):
        pass

stephen

@rownn to follow along @cvp if you have multiple TextViews you can use a separate object class with the methods and set that to the delegate for each and each will get their own so you wouldnt need to find out who is currently active


class MyTextViewDelegate(object):
    def TextView_should_begin_editing(self, textview):
        # resize and reposition for keyboard
        return True

    def TextView_did_begin_editing(self, textview):
        # keyboard presented
        pass

    def TextView_did_end_editing(self, textview):
        pass

    def TextView_should_return(self, textview):
        # resize and reposition for no keyboard
        textfield.end_editing()
        # keyboard not presented
        return True

    def TextView_should_change(self, textview, range, replacement):
        return True

    def TextView_did_change(self, textview):
        pass

stephen

@rownn both methoods should work. but if you want my opinion i would suggest usung @cvp 's objC route. i know mine works but i personally wish i learned objc_util when i was learning python. and this sounds like a perfect time to for yourself if you havnt already. you can even do my method using objc_util. using th UITextView if i recall correctly ๐Ÿค“

JonB

Of course you can always hook into the keyboard frame change notifications by implementing custom view for your root.

https://forum.omz-software.com/topic/5189/moving-the-keyboard-input-text-view-to-be-visible/4

stephen

@JonB i completely forgot about those methods Thank you ๐Ÿ˜Ž๐Ÿ˜…

rownn

Hi everyone,

my first idea was to detect if the TextView is focused or not, but sometimes I work with an external keyboard, so JonBs hint is perfect. It works perfectly fine for me. Hope there wonโ€˜t be any unexpected issues. Here the reduced code:

```
from scene import *
import ui

class MyScene (Scene):
def setup(self):
v = V(scene=self)
self.view.add_subview(v)

    background = Node(parent=self)
    background.position = (self.size.w/2,self.size.h/2)#(self.size.w/2, self.size.h/2)
    background.add_child(ShapeNode(ui.Path.rect(0,0,self.size.w,self.size.h), fill_color='#dddddd', stroke_color='clear'))

    self.add_child(background)

class V(ui.View):
def init(self, args, *kwargs):
self.scene = kwargs["scene"]
self.textView_H = 200
self.frame=(0, self.scene.size.h-self.textView_H, self.scene.size.w, self.scene.size.h)

    self.tv=ui.TextView(frame=(10, 0, self.scene.size.w-20, self.textView_H), font=('Courier', 17.0), background_color='#333333', text_color = '#ffffff')
    self.tv.text = 'Heyhey.'
    self.tv.delegate=self
    self.add_subview(self.tv)

def keyboard_frame_will_change(self, frame):
    self.animate(frame)

def animate(self, frame):
    def animation():
        self.y = self.scene.size.h-self.textView_H-frame[3]
    ui.animate(animation, duration=0.25)

if name == 'main':
run(MyScene(), PORTRAIT, show_fps=True)
```

Thank you guys. Amazing fast replying forum!

rownn

The animation part of the code seems to be not needed.
So...
```
def keyboard_frame_will_change(self, frame):
self.y = self.scene.size.h-self.textView_H-frame[3]
#self.animate(frame)

def animate(self, frame):

#def animation():
    #self.y = self.scene.size.h-self.textView_H-frame[3]
#ui.animate(animation, duration=0.25)
 ```
stephen

@rownn what JonB gave shoudnt give any issues. this is made exactly for what you need. i personally forgot all about theme lol

rownn

Great! Thanks:)

mikael

@rownn, the keyboard reveal delegates used to have serious issues, at least on the iPhone.

If you tried to use the exact frame height values they were very confusing, probably not properly handling the safe area. And also the handlers kept running after your script was done.

Would be happy to hear if these issues have been resolved in the intervening years.

JonB

Yes, also issues with landscape vs portrait vs upside-down, etc.