I found that you might have to wait two frames when adding variable height items, else it will not scroll to the absolute bottom of the last added object.
This is what my complete Log panel looks like. It can auto scroll to top, if new items added to top of log, or bottom when items added to end of list.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace YOUR_NS_HERE
{
public class LogPanel : MonoBehaviour
{
[SerializeField] private ScrollRect scrollRect = null;
[SerializeField] private RectTransform container = null;
[SerializeField] private GameObject msgFab = null;
[SerializeField] private bool addNewToTop = false;
public static LogPanel Instance { get; private set; }
private Queue<GameObject> messages = new Queue<GameObject>();
// ------------------------------------------------------------------------------------------------------------
private void Awake()
{
Instance = this;
// I keep the "message template" in the scene itself, so disable it now
msgFab.gameObject.SetActive(false);
}
private void OnDestroy()
{
Instance = null;
}
// ------------------------------------------------------------------------------------------------------------
public void ToggleVisible()
{
if (gameObject.activeSelf)
{
Hide();
}
else
{
Show();
}
}
public void Show()
{
gameObject.SetActive(true);
StartCoroutine(AutoScroll());
}
public void Hide()
{
gameObject.SetActive(false);
}
public void ClearAllMesssages()
{
GameObject go;
while (messages.Count > 0)
{
go = messages.Dequeue();
Destroy(go);
}
}
public void AddMessage(string message)
{
GameObject go = Instantiate(msgFab, container);
go.transform.GetChild(0).GetComponent<TMP_Text>().text = message;
go.gameObject.SetActive(true);
messages.Enqueue(go);
if (addNewToTop)
{
go.transform.SetAsFirstSibling();
}
// remove older messages if there are too many
if (messages.Count > Const.MaxLogMessages)
{
go = messages.Dequeue();
Destroy(go);
}
// auto-scroll
if (gameObject.activeSelf)
{
StartCoroutine(AutoScroll());
}
}
private IEnumerator AutoScroll()
{
LayoutRebuilder.ForceRebuildLayoutImmediate(container);
yield return new WaitForEndOfFrame();
yield return new WaitForEndOfFrame();
scrollRect.verticalNormalizedPosition = addNewToTop ? 1 : 0;
}
// ------------------------------------------------------------------------------------------------------------
}
}
I’ve attached the scene setup in case this is useful to anyone.
