Putting a multiline InputField in a Scroll Rect?

I’m working on making an in-game script editor for my game, and I’ve been trying to add a scroll bar to my multiline TextInput so that the line numbers and text input can scroll together. It works pretty good, but for some reason the Scroll Rect causes the InputField’s caret and selection highlighting to get messed up when I add any more lines. Here’s a .gif of the problem: Imgur

How can I accomplish this? What am I doing wrong?

Here’s an album that shows my setup: scrolling multiline input problem - Album on Imgur

EDIT: I’m still having this problem, any help would be appreciated.

I’ve had a simmilar issue and compiled a workaround until Unity fixes the InputField functionality.
All the combinations of layous/content fitters didn’t yield proper display of the last line.
The only downside is that you can’t fix the height of the input field without a ScrollRect component, also in the example.

Also, this example doesn’t cover the ScrollRect following the caret nor the selection in the InputField, if someone does that please post it here! :slight_smile:

HACKY SOLUTION:

InputFieldMod.cs - Unity InputFieldMod v1.0 - Pastebin.com

Example - http://illa3d.com/unity/inputfield_mod_v1.0.unitypackage

I was having a similar problem. The solution I found was simply create a game-object, obj1, to assign as a parent to the game-object with the input-field component, obj2. Then have the scroll-rect move the parent, obj1, which contains the child with the input-field component, obj2.

—>ScrollRect

-------->Obj1 <---- ScrollRect moves this one

-------------->Obj2 <— This one has input field component

This is my modified version of InputFieldMod, removing the highlight and take into account for canvas scale, etc:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using System.Collections;

namespace SweatyChair.UI
{

	[ExecuteInEditMode]
	[RequireComponent(typeof(InputField))]
	public class InputFieldScroller : UIBehaviour
	{

		[Tooltip("The default row count in  InputField, this will be ignored if a ScrollRect is assigned")]
		[Range(1, 50)] [SerializeField] private int _minRowCount = 1;

		[Tooltip("Scroll rect parent")]
		[SerializeField] private ScrollRect _scrollRect = null;

		private InputField _inputField;
		private RectTransform _rectTransform;

		// Layout
		private LayoutElement _layoutElement;
		private HorizontalOrVerticalLayoutGroup _parentLayoutGroup;

		private Vector2 _scrollRectSize;
		private float _canvasScaleFactor = 1;

		protected override void Awake()
		{
			_inputField = GetComponent<InputField>();
			_inputField.onValueChanged.AddListener(new UnityAction<string>(ResizeInput));
			_rectTransform = GetComponent<RectTransform>();
			CanvasScaler canvasScaler = GetComponentInParent<CanvasScaler>();
			if (canvasScaler)
				_canvasScaleFactor = canvasScaler.scaleFactor;
			_layoutElement = GetComponent<LayoutElement>();
			_parentLayoutGroup = transform.parent.GetComponent<HorizontalOrVerticalLayoutGroup>();
		}

		// Resize input field recttransform
		private void ResizeInput()
		{
			ResizeInput(_inputField.text);
		}

		private void ResizeInput(string text)
		{
			// Current text settings
			TextGenerationSettings settings = _inputField.textComponent.GetGenerationSettings(_inputField.textComponent.rectTransform.rect.size);
			settings.generateOutOfBounds = false;
			settings.scaleFactor = _canvasScaleFactor; // HACK: scale factor of settings not following the global scale factor... make sure it do

			// Get text padding (min max vertical offset for size calculation)
			float vecticalOffset = _inputField.placeholder.rectTransform.offsetMin.y - _inputField.placeholder.rectTransform.offsetMax.y;

			// Preferred text rect height
			float preferredHeight = (new TextGenerator().GetPreferredHeight(text, settings) / _canvasScaleFactor) + vecticalOffset + 10;
			float minHeight;

			// Default text rect height (fit to scroll parent or expand to fit text)
			if (_scrollRect)
				minHeight = _scrollRect.GetComponent<RectTransform>().rect.size.y;
			else
				minHeight = ((new TextGenerator().GetPreferredHeight("", settings) * _minRowCount) / _canvasScaleFactor) + vecticalOffset;

			// Current text rect height
			float currentHeight = _inputField.textComponent.rectTransform.rect.height;

			// Force resize
			if (Mathf.Abs(currentHeight - preferredHeight) > Mathf.Epsilon) {
				float newHeight = Mathf.Max(preferredHeight, minHeight); // At least min height
				if (_parentLayoutGroup && _layoutElement)
					_layoutElement.preferredHeight = newHeight;
				else
					_rectTransform.sizeDelta = new Vector2(_rectTransform.rect.width, newHeight);
			}

			// Scroll to bottom if just added new line
			if (gameObject.activeInHierarchy && _inputField.caretPosition == _inputField.text.Length && _inputField.text.Length > 0 && _inputField.text[_inputField.text.Length - 1] == '

')
StartCoroutine(ScrollToBottomCoroutine());
}

		// Update scroll rect position (after Layout was rebuilt)
		private IEnumerator ScrollToBottomCoroutine()
		{
			yield return new WaitForEndOfFrame();
			if (_scrollRect != null)
				_scrollRect.verticalNormalizedPosition = 0;
		}

	}

}