Move UI above TouchKeyboard for Mobile

Hi,

In our game, we have a chat area and would like to move the UI above the keyboard so that the last sent message is at the bottom of the visible space (like Facebook chat or WhatsApp etc does)

I’ve tried a few different ways to do this, but I’m having a hard time converting the TouchKeyboard height into something a RectTransform relates to. Is there an easy way to handle this that I’m missing? seems like it should be something fairly easy to do…

Thanks

I did something similar a while back. I will see if I can find the code, but I think I basically created a container within the canvas that contained everything so it could be shifted up easily.

IIRC I converted touch keyboard height into a percentage of the screen height and then used that percentage against the RectTransform anchored position.

Similar method to what I was using, I just can’t make the anchored positions make any sense with the percentages… its either too high and leaving a big huge gap, or on other resolutions its not moved enough

Ok, I’ve dug out the code.

I had two scripts - one which is on the input field and just checks when it becomes active and sets the transform (this app has about 500+ different input fields)

Then I have a second script which is attached to the containing game object (first item below the Canvas).
I used a bit of code I found elsewhere to get a corrected screen corners position: [Unity 4.6 Beta] Rect Transform Position (New UI System) - Questions & Answers - Unity Discussions for the GetScreenRect function, although I inverted the Y output from it. It could be this function is all you need

Ok my code below, it’s not pretty or tidy but seems to work smoothly for me across iPhone 4, iPhone 5, iPad portrait/horiz both retina and non-retina. This is all about making sure the input field is always visible above the keyboard.

This is currently for iOS - not tested on android as yet, hence the #if statements surrounding it so it falls back to not hiding the mobile input on android.

No idea how to format code properly here, sorry!

Apply this to the input field: (change MainContainer to whatever your top item is called)

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class InputfieldFocused : MonoBehaviour {

InputfieldSlideScreen slideScreen;
InputField inputField;

void Start () {
slideScreen = GameObject.Find (“MainContainer”).GetComponent();
inputField = transform.GetComponent();
#if UNITY_IOS
inputField.shouldHideMobileInput=true;
#endif
}

#if UNITY_IOS

void Update () {
if (inputField.isFocused)
{
// Input field focused, let the slide screen script know about it.
slideScreen.InputFieldActive = true;
slideScreen.childRectTransform = transform.GetComponent();
}
}
#endif
}

Now, apply this to the MainContainer (or whatever you call it)

using UnityEngine;
using System.Collections;

public class InputfieldSlideScreen : MonoBehaviour {

// Assign canvas here in editor
public Canvas canvas;

// Used internally - set by InputfieldFocused.cs
public bool InputFieldActive = false;
public RectTransform childRectTransform;

#if UNITY_IOS

void LateUpdate () {
if ((InputFieldActive)&&((TouchScreenKeyboard.visible)))
{
transform.GetComponent().anchoredPosition = Vector2.zero;

Vector3[ ] corners = {Vector3.zero,Vector3.zero,Vector3.zero,Vector3.zero};
Rect rect = GetScreenRect(corners,childRectTransform);
float keyboardHeight = TouchScreenKeyboard.area.height;

float heightPercentOfKeyboard = keyboardHeight/Screen.height100f;
float heightPercentOfInput = (Screen.height-(rect.y+rect.height))/Screen.height
100f;

if (heightPercentOfKeyboard>heightPercentOfInput)
{
// keyboard covers input field so move screen up to show keyboard
float differenceHeightPercent = heightPercentOfKeyboard - heightPercentOfInput;
float newYPos = transform.GetComponent().rect.height /100f*differenceHeightPercent;

Vector2 newAnchorPosition = Vector2.zero;
newAnchorPosition.y = newYPos;
transform.GetComponent().anchoredPosition = newAnchorPosition;
} else {
// Keyboard top is below the position of the input field, so leave screen anchored at zero
transform.GetComponent().anchoredPosition = Vector2.zero;
}
} else {
// No focus or touchscreen invisible, set screen anchor to zero
transform.GetComponent().anchoredPosition = Vector2.zero;
}
InputFieldActive = false;
}

public Rect GetScreenRect(Vector3[ ] corners,RectTransform rectTransform) {
rectTransform.GetWorldCorners(corners);

float xMin = float.PositiveInfinity, xMax = float.NegativeInfinity, yMin = float.PositiveInfinity, yMax = float.NegativeInfinity;
for (int i = 0; i < 4; ++i) {
// For Canvas mode Screen Space - Overlay there is no Camera; best solution I’ve found
// is to use RectTransformUtility.WorldToScreenPoint) with a null camera.
Vector3 screenCoord = RectTransformUtility.WorldToScreenPoint(null, corners*);*
if (screenCoord.x < xMin) xMin = screenCoord.x;
if (screenCoord.x > xMax) xMax = screenCoord.x;
if (screenCoord.y < yMin) yMin = screenCoord.y;
if (screenCoord.y > yMax) yMax = screenCoord.y;
corners = screenCoord;
}
Rect result = new Rect(xMin, Screen.height-yMin - (yMax - yMin), xMax - xMin, yMax - yMin);
return result;
}
#endif
}

Thanks for the code, I’ll have a look at integrating it into my game.

Hi,

I’ve just been working on this code, but found an issue in the GetScreenRect code. WorldToScreenPoint doesn’t take an array of Vector3’s, at least not in Unity 4.6… what version are you using or is this a mistake?

should this be corners and when assigning back, coners = screenCoord?

Hi there @kujo

I’m working on something similar and running into the same issue. Did you happen to figure out the error?

I’ve updated the second script so that it works with Unity 5.X. and fixes the error in the above script:

using UnityEngine;
using System.Collections;

public class InputfieldSlideScreen : MonoBehaviour {

    // Assign canvas here in editor
    public Canvas canvas;


    // Used internally - set by InputfieldFocused.cs
    public bool InputFieldActive = false;
    public RectTransform childRectTransform;

    #if UNITY_IOS


    void LateUpdate () {
        if ((InputFieldActive)&&((TouchScreenKeyboard.visible)))
        {
            transform.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;

            Vector3[] corners = {Vector3.zero,Vector3.zero,Vector3.zero,Vector3.zero};
            Rect rect = RectTransformExtension.GetScreenRect(childRectTransform, canvas);
            float keyboardHeight = TouchScreenKeyboard.area.height;

            float heightPercentOfKeyboard = keyboardHeight/Screen.height*100f;
            float heightPercentOfInput = (Screen.height-(rect.y+rect.height))/Screen.height*100f;


            if (heightPercentOfKeyboard>heightPercentOfInput)
            {
                // keyboard covers input field so move screen up to show keyboard
                float differenceHeightPercent = heightPercentOfKeyboard - heightPercentOfInput;
                float newYPos = transform.GetComponent<RectTransform>().rect.height /100f*differenceHeightPercent;

                Vector2 newAnchorPosition = Vector2.zero;
                newAnchorPosition.y = newYPos;
                transform.GetComponent<RectTransform>().anchoredPosition = newAnchorPosition;
            } else {
                // Keyboard top is below the position of the input field, so leave screen anchored at zero
                transform.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
            }
        } else {
            // No focus or touchscreen invisible, set screen anchor to zero
            transform.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
        }
        InputFieldActive = false;
    }

}
   
public static class RectTransformExtension {

    public static Rect GetScreenRect(this RectTransform rectTransform, Canvas canvas) {

        Vector3[] corners = new Vector3[4];
        Vector3[] screenCorners = new Vector3[2];

        rectTransform.GetWorldCorners(corners);

        if (canvas.renderMode == RenderMode.ScreenSpaceCamera || canvas.renderMode == RenderMode.WorldSpace)
        {
            screenCorners[0] = RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, corners[1]);
            screenCorners[1] = RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, corners[3]);
        }
        else
        {
            screenCorners[0] = RectTransformUtility.WorldToScreenPoint(null, corners[1]);
            screenCorners[1] = RectTransformUtility.WorldToScreenPoint(null, corners[3]);
        }

        screenCorners[0].y = Screen.height - screenCorners[0].y;
        screenCorners[1].y = Screen.height - screenCorners[1].y;

        return new Rect(screenCorners[0], screenCorners[1] - screenCorners[0]);
    }

}    #endif
2 Likes

Also, rather than doing a GameObject.Find(“MainContainer”) and being hardcoded to that name, i changed the assignment line from

slideScreen = GameObject.Find (“MainContainer”).GetComponent();
|
to
|
slideScreen = gameObject.GetComponentInParent();

This will pick walk up from the input field that the script is attached to, and find the first “InputfieldSlideScreen” it can find, which is a must for me, as i want to be able to have different “shifters” throughout my app, with different naming/structures.

1 Like

Tested with Android, not working.

Any suggestion ?

Unity does not have much Android native code. Im currently looking for some plugin which would disable it. Still nothing.

Try to use it:

public static int GetKeyboardHeight(bool includeInput)
    {
#if UNITY_ANDROID
        using (AndroidJavaClass UnityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            AndroidJavaObject View = UnityClass.GetStatic<AndroidJavaObject>("currentActivity").Get<AndroidJavaObject>("mUnityPlayer").Call<AndroidJavaObject>("getView");

            using (AndroidJavaObject Rct = new AndroidJavaObject("android.graphics.Rect"))
            {
                View.Call("getWindowVisibleDisplayFrame", Rct);

                int height = Rct.Call<int>("height");
                int width = Rct.Call<int>("width");

                int systemHeight = Display.main.systemHeight;
                int systemWidth = Display.main.systemWidth;

                return Screen.height - Rct.Call<int>("height");
            }
        }
#elif UNITY_IOS
        return (int)TouchScreenKeyboard.area.height;
#else
        return 0;
#endif
    }
4 Likes

I have an issue with currentActivity field - it simply cannot find it in unity editor. Web says it’s fine - on build everything should work but it doesn’t. I don’t even know how to check if it returns actual keyboard height during Android build
By the way what is the best way to debug such Android builds? Keyboard doesn’t want to display properly in emulators

You can debug by build a Development Build and check Script Debugging (you can use the build and run option to speed up the device deploy):

this is the only way I know for proper debugging on Android.

1 Like

Thnx a lot! I’ve finally done it!

Moreover, this solution only works if your screen is the same as canvas. Otherwise, for example, if you set UI Scale mode to “Scale with screen size” you have to find relative to canvas keyboard height. To do this simply try this:

 float KBrelativeTOscreen = MobileUtilities.GetKeyboardHeight(true);
            float screenToCanvasRatio = Screen.height / canvas.rect.height;

            float KBrelativeToCanvas = KBrelativeTOscreen / screenToCanvasRatio;

             fullChatOverlay.anchoredPosition = new Vector2(0f, KBrelativeToCanvas);

it’s a little off-top, but it solves the next problem - to resize properly chat-box(or whatever) using this value.

p.s. Do not forget to use float instead of int - or, more likely, it will break everything.:slight_smile:

2 Likes

I got this to work relative to the canvas size, here is the code… All you need is to call it with the canvas RectTransform reference.

    public static int GetRelativeKeyboardHeight(RectTransform rectTransform, bool includeInput)
        {
            int keyboardHeight = GetKeyboardHeight(includeInput);
            float screenToRectRatio = Screen.height / rectTransform.rect.height;
            float keyboardHeightRelativeToRect = keyboardHeight / screenToRectRatio;
   
            return (int) keyboardHeightRelativeToRect;
        }
   
        private static int GetKeyboardHeight(bool includeInput)
        {
    #if UNITY_EDITOR
            return 0;
    #elif UNITY_ANDROID
            using (AndroidJavaClass unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
            {
                AndroidJavaObject unityPlayer = unityClass.GetStatic<AndroidJavaObject>("currentActivity").Get<AndroidJavaObject>("mUnityPlayer");
                AndroidJavaObject view = unityPlayer.Call<AndroidJavaObject>("getView");
                AndroidJavaObject dialog = unityPlayer.Get<AndroidJavaObject>("mSoftInputDialog");
                if (view == null || dialog == null)
                    return 0;
                var decorHeight = 0;
                if (includeInput)
                {
                    AndroidJavaObject decorView = dialog.Call<AndroidJavaObject>("getWindow").Call<AndroidJavaObject>("getDecorView");
                    if (decorView != null)
                        decorHeight = decorView.Call<int>("getHeight");
                }
                using (AndroidJavaObject rect = new AndroidJavaObject("android.graphics.Rect"))
                {
                    view.Call("getWindowVisibleDisplayFrame", rect);
                    return Screen.height - rect.Call<int>("height") + decorHeight;
                }
            }
    #elif UNITY_IOS
            return (int)TouchScreenKeyboard.area.height;
    #endif
        }
4 Likes
TouchScreenKeyboard.area.height

I got a full screen height with this code on iOS. What am I doing wrong?
Unity 2019.4.12f, iPhone 6

Oh, gosh It was long ago. As far as I remember it should work on IOS - there isn’t any problem with it

1 Like

I have the same problem on IOS: when i don’t make Open for TouchScreenKeyboard (it was opened when was touch in Webview form) and try to get TouchScreenKeyboard.area.height - it returns value that equals to Screen.height

Unfortunately, I have tested it only for android, and you have to probably do some research for your own, if it is not working as expected(Probably it involves some native code manipulation, but I am not completely sure). btw you can pin some code to have a look here.