2
\$\begingroup\$

After applying a text change to a scrolling view, how do you get the new height of the text, resize other UI elements around the changed text length, etc?

Many have posted this question. Some have noted that the rect size doesn't change or that they have to wait multiple frames to then react to the UI updates.

\$\endgroup\$
4
  • \$\begingroup\$ I can't really tell if this is a question or a tutorial. What's your actual question? If it's an answer to the question you mentioned, you need to post it as an answer to that question. \$\endgroup\$
    – House
    Sep 24, 2015 at 18:11
  • \$\begingroup\$ OK, it's just a question now. \$\endgroup\$
    – Dustin_00
    Sep 24, 2015 at 19:26
  • \$\begingroup\$ Can you maybe give an example of the situation you're having the problem with? Also, if many have posted this question, surely there is a duplicate already? How are those other answers not answering your question? \$\endgroup\$
    – House
    Sep 24, 2015 at 20:58
  • \$\begingroup\$ If the text in the box fits, hide the scroll bar. If the text exceeds the view, show the scroll bar. Other questions: stackoverflow.com/questions/25637659/… -- leverages the Content Size Fitter (note: 0 events), but that interaction happens behind the scenes so there's no way to hook other UI elements into updating with it. No answer over in Unity to answers.unity3d.com/questions/921726/… \$\endgroup\$
    – Dustin_00
    Sep 24, 2015 at 22:20

2 Answers 2

3
\$\begingroup\$

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:

  1. Finds the ScrollTextMain GameObject by name
  2. Finds the ScrollBarMain GameObject by name
  3. 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.
  4. 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

  1. 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.
  2. 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:

  1. 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;
        }

    }
}
  1. Add a UI -> Button to your Canvas, Name: SendTextButton

  2. Attach the TestAction.cs script to it.

  3. In the Button's On Click event, add the SendTextButton object, then select the TestAction => OnClick function

  4. Run your app and click the button.

\$\endgroup\$
0
\$\begingroup\$
  1. Set Text and mark component as dirty;
  2. At the beginning of next Update tick, check if component is dirty;
  3. If it is - Do the work...

    using UnityEngine;
    using UnityEngine.UI;
    
    public class LogController : MonoBehaviour
    {
         public Text label;
         public ScrollRect scroll;
         bool isDirty;
    
         public void SetText(string text)
         {
            label.text = text;
            isDirty = true;
         }
    
         void Update() 
         {
            if(isDirty)
            {
                scroll.verticalNormalizedPosition = 0;
                isDirty = false;
            }
         }
    }
    

Game Objects Hierarhy is:

  • ScrollRect
    • ViewPort
      • Text
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .