When editing a textbox on mobile, the UI shifts vertically to focus on the text box in some way.
- Can you confirm that this vertical shift is to center the TextBox? I don’t believe this is an OS level thing, likely it’s some Roblox platform code which does this shift.
- Is there a way to disable the shifts completely so we can handle altering our UI to make the textboxes visible, just like in the mobile emulator? If it is indeed platform code, it would be nice to have a UserInputService property to disable this shifting behavior.
- I have a workaround below, please let me know if this is the best and simplest way.
Minor issues:
- The emulator behavior is not consistent with iOS in three ways:
- It doesn’t do the UI shift.
- The keyboard height does not seem to be correct. There is a white bar ~28px high on top of the keyboard (probably for suggestions) which doesn’t seem to be accounted for.
- There is an inconsistency about when the emulator changes the OnScreenKeyboardSize (it appears to do it immediately upon CaptureFocus() whereas iOS takes a little while longer as it’s popping up.)
Context
For context, we are trying to integrate some textboxes into our game. We are running into the issue of the textboxes becoming partially obscured by the shift. If we could determine what exactly the shift was, we could change the UI to reposition the textbox so it was completely in view.
Repro
To repro this issue, put a large textbox on the screen. You can see the screen shift so that it is centered.
Related posts
- @FactorOfTheThird complained about the same issue.
- This post outlines a few other people struggling with it.
Hypothesis
My hypothesis based on playing around with this is that Roblox will shift the UI up so that the textbox is vertically centered within the viewport.
Workaround
workaround.rbxl (63.1 KB)
Above is a workaround. On the left is a default textbox, on the right is the workaround. I have made it work consistently in both Studio and on an iOS device Here is how it works (see the LocalScript in StarterPlayerScripts):
- A button is painted over the textbox to intercept touches to the textbox (there isn’t any WillFocus event on the TextBox and we need to do some stuff before the TextBox fires Focused)
- We disable the button, then capture focus for the first time, waiting until the keyboard pops up.
- We read the size of the keyboard and screen to determine the safe area.
- We lose focus of the textbox to prepare to resize and reposition it.
- We apply the resizing and repositioning to the TextBox.
- We wait 2 heartbeats for the UI to repaint.
- We capture the focus and set up the FocusLost handler to reset the UI after editing.
This seems overly complex but I’m not sure there is any other way of doing it.
Thanks for taking a look at this!
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local LocalPlayer = Players.LocalPlayer or Players.PlayerAdded:Wait()
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
local ScreenGui = PlayerGui:WaitForChild("ScreenGui") :: ScreenGui
local frame = ScreenGui:WaitForChild("Frame") :: Frame
local textbox = frame:WaitForChild("TextBox") :: TextBox
local button = frame:WaitForChild("TextButton") :: TextButton
-- The keyboard size doesn't change in the emulator
local waitUntilKeyboardShownOrHidden = function()
if RunService:IsStudio() then
task.wait()
else
UserInputService:GetPropertyChangedSignal("OnScreenKeyboardSize"):Wait()
end
end
button.Activated:Connect(function()
-- Disable the button
button.Active = false
button.Visible = false
-- We need to get the onscreen keyboard height to determine the safe area
-- The size on iOS is not present until the keyboard actually pops up
textbox:CaptureFocus()
waitUntilKeyboardShownOrHidden()
local onScreenKeyboardHeight = UserInputService.OnScreenKeyboardSize.Y
if RunService:IsStudio() then
-- The keyboard height is not accurate in Studio emulator due to some white bar
-- Happens to be approx 28 pixels tall.
onScreenKeyboardHeight += 28
end
local screenSize = ScreenGui.AbsoluteSize.Y
local maxSafeSize = screenSize - onScreenKeyboardHeight
-- Now pop down the keyboard so we can resize the textbox
textbox:ReleaseFocus()
waitUntilKeyboardShownOrHidden()
-- Now we can resize the textbox properly
-- Add a buffer of 6 pixels so we can show that the entire textbox is rendered
local bufferSize = 6
local oldPosition = frame.Position
local oldSize = frame.Size
frame.Size = UDim2.fromOffset(frame.AbsoluteSize.X, maxSafeSize - bufferSize)
frame.Position = UDim2.fromOffset(frame.Position.X, 0 + bufferSize / 2)
-- And the Engine spake, saying, 'First shalt thou resize thy Text Box.
-- Then shalt thou task.wait() to two, no more, no less. Two shall be the number thou shalt task.wait(),
-- and the number of the task.wait() shall be two. Three shalt thou not task.wait(), neither task.wait() thou one,
-- excepting that thou then proceed to two. Four is right out. Once the number two, being the second number,
-- be reached, then capture focus thou thy Roblox Text Box on thy screen, who being dirty in My sight,
-- shall be rerendered natively.'
task.wait()
task.wait()
textbox:CaptureFocus()
textbox.FocusLost:Once(function()
button.Active = true
button.Visible = true
frame.Position = oldPosition
frame.Size = oldSize
end)
end)