This is one method of updating a ScrollBar (or anything) after changing the content of ScrollView Text and wanting to react to the new width/height of the text after it is rendered.
Platform: Unity 4.6 / 5 (I'm using Unity 5, but I think I used all 4.6 components), C#
There are a number of videos on how to create scrolling text views for fixed-content:
Unity: https://www.youtube.com/watch?v=tcU8yzv_xEw
Sloan Kelly's popular: https://www.youtube.com/watch?v=5XJBBWI2F0I
There are a many questions posted of how to resize things / update their UI after changing Text.
How to create scrolling input text field using Unity UI
There are very few answers.
Here is my approach.
Goals:
Simple to apply to a project.
Minimize artifacts.
Minimize impact on performance
Debug-able for when you get extra fancy
Initially I tried Coroutines and other tricks to try to let the ScrollView update and then capture the new dimensions.
This did not go well.And multi-threading can make testing ugly.
Usage:
Create a root empty GameObject in your scene.I rename it to GlobalTextScript.
Apply the below script to that object.
Create the following GameObject structure in your Hierarchy:
- UI->Canvas
- UI->Panel : name "ScrollTextPanel", Anchor: custom(place where you want the scroll view)
- UI->ScrollBar : name "ScrollBarMain", Direction: Bottom to Top(in most cases), Anchor: Alt - Shift Stretch, Right(Rect Transform Width: 20(or to taste)), Value: 1(moves scroll to top)
- UI->Image : name "ScrollBackground", Anchor: Alt - Shift Stretch Stretch(then set Rect Transform Right: [Match your ScrollBarMain Right Width])
- Components: Mask, Scroll Rect(Horizontal: off, Vertical Scrollbar: ScrollBarMain)
- UI->Text: name "ScrollTextMain", Anchor: Alt - Shift Top Left
- Components: Content Size Filter(Horiz: Unconstrained, Vert: Preferred Size)
- With this created, go back to ScrollBackground and add ScrollTextMain to the Scroll Rect's Content value
This script at launch:
- Finds the ScrollTextMain GameObject by name
- Finds the ScrollBarMain GameObject by name
- This means you don't have to have them as public variables and drag-and-drop the objects into them in the IDE.
- I've had the IDE lose those values too many times, I don't trust it and prefer it "documented" in the code what the variable should point to.
- Grabs the child components it's actually interested in.
- Since the script immediately will go into an Update cycle, it will wipe your IDE content
- Start() has some commented out code you can enable to prevent this.
Usage during run time
- A simplistic Singleton allows access globally through GlobalTextScript.Instance.UpdateText(string).
- This means if you want multiple text areas managed, you will need to clone this class under a different name
- Alternately, you can add a bunch of interesting code to hold all the Text and ScrollBar objects and each.UpdateText will need to say what it's target is.
- That's it.
When.UpdateText(string) is called, it's sets itself up to perform the update over 4 frames.
1st frame: it actually updates the text content of the target.
2nd frame: it waits -- after this frame Unity's UI engine will update the ScrollBar.size value
3rd frame: Your update code goes here as you can now look at ScrollBar.Size and get a valid answer.
3rd frame (b): If you add variables to track the ScrollBackground's Transform, I assume you could look at it's size here too. I haven't tried yet.
4th frame: goes inactive, taking itself out of Unity's Update loop and minimizing impact on perf.
Full GlobalTextScript.cs code:
using UnityEngine;
using System.Collections;
public class GlobalTextScript : MonoBehaviour
{
private GameObject _ScrollTextMain; // if you need to debug in Unity IDE, change this to public
private UnityEngine.UI.Text _Text;
private GameObject _ScrollBarMain; // if you need to debug in Unity IDE, change this to public
private UnityEngine.UI.Scrollbar _ScrollBar;
private int _FrameCount = 0;
private string _NewText = string.Empty;
void Start()
{
_ScrollTextMain = GameObject.Find("ScrollTextMain");
_ScrollBarMain = GameObject.Find("ScrollBarMain");
_Text = _ScrollTextMain.GetComponent<UnityEngine.UI.Text>();
_ScrollBar = _ScrollBarMain.GetComponent<UnityEngine.UI.Scrollbar>();
// On Start, the Update loop is going ot overwrite your scrollview text with _NewText ("").
// If you have IDE default text you want to preserve, add this line in:
// gameObject.SetActive(false);
// or this one:
// _NewText = _Text.text;
}
public void UpdateText(string text)
{
// note: an in-progress update will be flat out reset by a new incoming text.
_NewText = text;
_FrameCount = 0;
gameObject.SetActive(true);
}
// Once UpdateText is called, over 3 frames, the text scroll view is updated, then the object goes back to sleep so it doesn't need Updates
void Update()
{
switch (++_FrameCount)
{
case 1: // update text -- technically can be in the UpdateText function, but this shows the frame flow
Debug.Log("frame: 1");
_Text.text = _NewText;
break;
case 2: // Unity UI updates _ScrollBar.size AFTER this Update
Debug.Log("frame: 2, size: " + _ScrollBar.size);
break;
case 3: // update ScrollBar
Debug.Log("frame: 3, size: " + _ScrollBar.size);
_ScrollBarMain.SetActive(_ScrollBar.size < 0.995);
break;
case 4: // go back to sleep
Debug.Log("frame: 4");
gameObject.SetActive(false);
break;
default:
Debug.LogError("GlobalTextScript is on frame: " + _FrameCount);
break;
}
}
#region Simple Singleton
public static GlobalTextScript Instance;
void Awake()
{
Instance = this;
}
#endregion Simple Singleton
}
A quick Demo:
Start a new project, and add the GameObjects outlined above, then:
- Create a new script called TestAction.cs
Put this code in it:
public class TestAction : MonoBehaviour
{
private int _Count = 0;
public void OnClick()
{
_Count = (_Count + 1) % 3;
Debug.Log("count: " + _Count);
switch (_Count)
{
case 0: GlobalTextScript.Instance.UpdateText("Testing..."); break;
case 1: GlobalTextScript.Instance.UpdateText("Testing...\n1\n2\n3\n4\n5\n6"); break;
case 2: GlobalTextScript.Instance.UpdateText("Testing...\n1\n2\n3\n4\n5\n6\n1\n2\n3\n4\n5\n6\n1\n2\n3\n4\n5\n6\n1\n2\n3\n4\n5\n6\n1\n2\n3\n4\n5\n6\n1\n2\n3\n4\n5\n6\nEnd"); break;
default: GlobalTextScript.Instance.UpdateText(""); break;
}
}
}
Add a UI -> Button to your Canvas, Name: SendTextButton
Attach the TestAction.cs script to it.
In the Button's On Click event, add the SendTextButton object, then select the TestAction => OnClick function
Run your app and click the button.