Can someone explain how to handle UI updates being ignored due to GameObject toggling and other mysteries?

I’ve been putting together a bunch of UI windows and elements the last few weeks and been pretty content with the direct relation between the limits of what I know and what I can do. One thing, however, has been blocking me over and over and I can’t seem to find a clean explanation of the pattern I’m missing. It prevents me from signing off on my scripts and introduces a lot of mess as I desperately spin around in circles trying to fix it.

Issue
Opening and closing is an inherent action for UI, yet every time I set up my initial updates for a UI, I run into cases where visual updates are being skipped. Tabs will load their pages, but the Selectable on the tab itself won’t receive a styling unless I make redundant calls to refresh it. Modals will load, canvases will toggle, but they won’t resize unless I insert coroutines that delay the action for a few frames or toggle on and off manually. I have one script that needs to wait at least 3 frames before it will successfully draw the changes I sent to it and I have no clue where that number comes from.

In all of those cases, the code that’s skipped is identical to the code that ends up running successfully. It’s a timing issue as far as I can tell and as far as my team’s been able to suggest. It primarily affects when UI is loaded for the first time, and doesn’t so much affect refreshing done to an already open canvas. The Selectable state on my tabs will be marked correctly, but the visual state won’t match.

I assume I’m missing something critical about the order of operations for draw actions, and I’m also missing how to either avoid or adapt to it. I’m pretty sure this is a common pattern I just haven’t gotten into my head yet.


The number of frames I have to wait feels arbitrary. The updates which do or don’t get skipped feel arbitrary. I’m guessing blindly and sticking this pattern wherever I run into it in the hopes that it’ll work.


if (refreshCoroutine != null) { StopCoroutine(refreshCoroutine); }
refreshCoroutine = StartCoroutine(RefreshUI(1));

IEnumerator RefreshUI(int frameDelay = 3)
{
     for (int i = 0; i < frameDelay; i++) { yield return null; }
     UpdateThing();
}

I’ve tried switching between toggling a UI’s canvas or its canvas’s GameObject and the same problem occurs. I’ve tried running Canvas.ForceUpdateCanvases(); with no change. I’ve tried enabling, then updating, or reversing the pattern.

Any help would be appreciated as I’m in full guess mode right now which is not a smart way to code.

1 Like

it might be something with the routine’s logic itself, maybe it doesn’t update when you think it is. generally, relying on a few frames until you update something sounds like a bad idea because it might break later once your project evolves. ideally, you’d find the root cause first (but I know it might take too much time). haven’t tested this, but if you’re short on time, you can also get some inspiration from this:

public void ToggleUIElement(GameObject uiElement)
{
    StartCoroutine(RefreshUI(uiElement));
}

IEnumerator RefreshUI(GameObject uiElement)
{
    uiElement.SetActive(true);
    Canvas.ForceUpdateCanvases();
    yield return null;
    Canvas.ForceUpdateCanvases();
    UpdateThing();
}
  • if UpdateThing() is the call that doesn’t update the view, then place it first

alternatively, you can use LayoutRebuilder.ForceRebuildLayoutImmediate() (and pass the transforms) in place of the more expensive Canvas.ForceUpdateCanvases()

As I mentioned, I already tried Canvas.ForceUpdateCanvases() and it didn’t fix this issue or make any apparent change at all.

The coroutine was one of my first attempts to fix the issue which I originally thought was caused by double-assigning properties in the same frame. That proved not to be the case, but the solution itself has had partial success. The frame delay counter is necessary, as I’ve solved at least one instance simply by waiting 3 frames, rather than 1, as I said. Your example would have remained broken, with no intuitive way to realize that it could be resolved by waiting twice more.

I’ve “fixed” this issue several times in several ways, but can’t find either a consistent number of frames or even a consistent solution. I’m currently hours deep into trying to force the following example despite it having resolved on other scripts using the delay method.


Example Problem
A Toggle in a ToggleGroup is correctly receiving its isOn and interactable states through code, but not updating its visuals to reflect the state.

Hovering or otherwise interacting with the toggle refreshes that individual element, but in the case of a ToggleGroup leaves all other toggles without update.


Attempts to Fix
Reselecting the properties on all toggles through code (isOn = isOn, ec.) with or without notifying does not cause those visual states to update.

Reselecting several frames later using a coroutine doesn’t update them. Waiting 100 frames doesn’t update them.

Canvas.ForceUpdateCanvases() doesn’t update them.

Graphic.SetAllDirty() doesn’t update them, regardless of whether frames are waited.

ToggleGroup.NotifyToggleIsOn(activeToggle) doesn’t update them.

Running any of these manually through an inspector context menu to remove issues with timing doesn’t update them.


How do I make a Toggle update visually through code?

I mean if the UI is being drawn and you are - via code - changing the state of it, it should just redraw automatically.

If it’s not, then you are either not modifying the UI objects you think you are (such as modifying a prefab as opposed to the in-scene instance), or there is some major bug. Though I feel like the latter is unlikely as we’d probably be seeing other posts about this.

But it sounds like, while not explicitly stated, that you are modifying these elements while their respective game objects are inactive? They probably don’t redraw when inactive, and should redraw when made active.

The objects and their components are active in scene (all toggling resolves before the delay), and if I click the components their live reporting of their states is correct. Visually their Selectable state doesn’t change to match what the component is reporting.

The active state was pointed out to me as a potential source of the problem and in a few cases seemed like it led me to a solution with the delaying frames idea. Now I’m just lost again and it’s driving me crazy.

There is a chance that, while the initial assignment is failing for timing related reasons, it’s possible that my forced refresh is failing for a different reason. Namely, that I’m using structs to store quick references to the components so I don’t have to run quite so many GetComponentsInChildren<>() calls which is itself only necessary because I’m trying to fix this issue.

Going to try replacing those with mini classes and see if the references are being instanced and calls lost to the wind. That really shouldn’t be it since the states are continuously being passed and updated despite using the same references.

I mean break it down to the most simple test. Have a scene with naught but a toggle, and a component to change it (on space or something). Does it change? Work upwards from there.

1 Like

I think part of the issue in this particular case is that I assumed SetIsOnWithoutNotify was suppressing the event for external purposes, yet changing all calls back to standard isOn = x seems to have removed a lot of the issue. I feel like I need a half-notify option now to distinguish between blocking the internal Selectable state (which I don’t want) and blocking the event used by external scripts (which I do).

The frustrating part is that I explicitly called the isOn = x variant in my refresh method which is how I tested that it was equally blocked with or without notification. I looped through every toggle in the group and reapplied their current isOn states. I tried notifying only for the enabled toggle and not the disabled toggles since those often call redundantly.

I’m also not sure why I have two different solutions for what seemed like the same problem. I would have hoped the first fix would work for the second. Guess I’ve got two very similar looking issues with my code. I still have to figure out how to understand and avoid those original series of bugs though. Will update if I hit it again.

To be fair, I used to maintain an asset on the store that heavily relied on uGUI and it was a total nightmare. It was rife with bugs, many which would revert to being fixed and broken with every other update to Unity 5. I had exactly these kinds of issues when trying to update certain things based on events. It go so bad that I ended up relying on forcing the UI to redraw manually every frame.

Don’t disagree, but they are force-redrawing the UI here and the issue persists. So either they’re doing something funky, or something is really wrong has been introduced into recent uGUI versions. Though I don’t think the package has had a real update for a long while?

(I prefer UI Toolkit in any case.)

This was a terrible bug/issue I had back with NGUI (Delaying things to fix the issues was a hack there and making sure things were visible if tweens involved).

But on using UGUI I can not say I have had any major issues so your problem is odd. Having tween animations is perhaps a minor issue if you show them and set them to a state they do not start in meaning they animate to the starting state.

Sounds a complex UI setup so you may have to break it down and start with simple layouts until you get to where it breaks

Yeah, it’s unfortunately the type of schedule where I have to keep moving forward and building on top of my best efforts at each stage. I did significantly improve how consciously I was applying certain UI changes in my flow as I went, but it didn’t prevent me from running into that last wall anyway. I had hoped I could learn a more intuitive and robust avoidance pattern but it sounds like there’s a lot of opportunities for making mistakes.

What code did you use to force the redraw? I had intended to stay high level and ignore concepts like the timing of draws in the UI, but since I can’t predict the full effects of various actions, I’m forced to learn that belatedly. New strategy is just to collect a pile of options and run them all to see if I can fix it. Whatever issues are left are either something new, or of my own making.

Erm, by destroying and recreating all of the UI elements… Yeah, it wasn’t pretty. And you absolutely don’t want to do it that way for anything particularly complex or that needs to be displayed while also running your normal game logic and graphics. As an aside, I looked back at the code (it’s been about eight years since I wrote it) and I was at least sane enough to only perform this ‘update’ when something actually changed in the backing model and not actually every single frame.

However, you might be able to speed it up by instead invalidating the UI so that Unity is forced to manually recreate the entire UI mesh. That will still hurt performance but at least you avoid the issue of destroying and recreating managed objects which is far far worse.

Ouch, I don’t have quite enough control over my UI for that since I didn’t expect it to be as much of an issue as it’s been. I have built it with a lot of structure relative to what I’ve worked with in the past, though. It’s got something closer to the model/view workflow so my engineers can link their data to it and drive the flow using intuitive methods at the top, and the UI propagates everything they need without their digging around making a mess.

The effort gap between smooth sailing and so much as a single snag with UI can be immense. It feels a bit like having to stop in the middle of cooking rice to search for the plastic bead you swore you saw fall into to pot. If you’re new to cooking, that’s a disaster that’s going to cost you the meal or the rest of your evening, yet it would be naive to never try cooking rice since it’s not intended to be a difficult experience. With UI systems, you should be able to do as much as possible without needing to tap into your bug sleuthing skills to chase down an under-documented misconception using information from a thread from the dawn of time.

My issues have fortunately been limited (mostly) to the most interactable elements which tend to be closer to my scripts where I can address them better.

I agree with this… UGUI has easily been the most stable least changing Unity subsystem of the past 12 years I think. It’s still at 1.0.0 and half the world relies on it deeply in millions of scenes and prefabs all over the place, and I can’t even remember the last time I saw it mess up activation / updating.

I accept that you’re seeing issues but as Spiney said, it shouldn’t take long to isolate either the bug is on your side or on Unity’s side. If you can prove it’s on Unity, by all means file a bug report!! That’s the only way this stuff gets fixed.

Bugs can get reported right from Unity3D:

Help → Report A Bug.

Report one bug at a time with all the expected supporting information attached.

Only time you should need to manually force a update is if you have some dynamic logic that wants to do its thing after the UI have done its thing. For example I do this for a tooltip I have created i place it by the mouse pointer hovering label then populated the tooltip. After this I might want to constraint the width so its not over drawing the canvas / parent and todo that I force a update and then check the resulting width / height.

I would say this is the only logical reason to manually force a update to a uGUI element.