- Home /
How do I layout dynamically instantiated UGUI components?
TLDR: I am dynamically instantiating item renderers from a prefab for a UGUI List component I am making. I don't know how to layout the List in script because the item renderer's RectTransform.rect does not specify a width or height until the next frame. How do I layout dynamically instantiated UGUI components properly?
Background: I am currently building a UI component library (think List, ComboBox, etc.) using UGUI. The goal is to create completely dynamic and reusable components that can be dropped into any UI layout. I have had some very good success so far, but I have come a question in the process: how do you dynamically layout components (properly) using the UGUI framework?
UGUI Background Resources Unity Documentation: http://docs.unity3d.com/Manual/UISystem.html
UGUI Source: https://bitbucket.org/Unity-Technologies/ui/src/
I am currently working on a dynamic list component. Hopefully these are some requirements that will help explain what I am working on and some of the challgenges I am having:
The List shall be a prefab.
The List shall allow a designer to drag-and-drop the list prefab into any UI without needing custom adjustments.
The List shall allow a designer to assign any "item renderer" component that will be rendered for each visible data object within the list.
The List shall be driven by a "data provider" that supports any data type.
The List shall automatically create, destroy, and otherwise manage a series of "item renderers" that are use to display the data in the "data provider."
The List shall render default "item renderers" of the assigned type while in Edit mode for a designer to preview how it looks.
The List shall allow a designer to specify the maximum number of visible rows (if vertical) or columns (if horizontal) that can be displayed at one time.
The List shall allow a designer to toggle between "vertical" and "horizontal" layouts.
I have been able to successfully implement almost all of those requirements, however I have not been successful in laying out the item renderers in a single Update. The root of the problem is in dynamically instantiating the item renderers. For simplicity, lets say one item renderer gets instantiated for each item in the data provider. When each item renderer is instantiated its RectTransform has no width or height. This means I cannot properly layout the component since I am working with unknown dimensions. Is it possible to measure the item renderer in the same frame that it is instantiated?
I currently have an invalidate cycle in the Update function. When any of the lists properties are modified the component trips a flag "invalidateProperties." Then in the Update loop I check to see if the flag is true, and if so I perform a layout based on what changed. This includes creating new item renderers, destroying extra item renderers, updating item renderers with data so they can initialize their own displays, and performing an automatic layout in the case of toggling the horizontal or vertical property on the list. During this invalidate cycle I cannot get the RectTransform.rect to calculate its size in the current frame on newly instantiated item renderers. This seems to be because Unity's layout process takes place after the current frame. Yes, I have also tried checking in LateUpdate, the RectTransform.rect still is not initialized there either.
In parsing through some of the UGUI source I have found an interesting call: CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild()
Unity Documentation: http://docs.unity3d.com/ScriptReference/UI.CanvasUpdateRegistry.html
It appears this allows you to recieve callbacks for Prelayout, Layout, and PostLayout. This is a good start because I should probably be moving my item renderer layout to one of these events. The problem is that the item renderers dimensions are still not initialized until PostLayout. Even doing this, performing a layout does not work properly. It appears that the layout is being correctly updated, however it is not "refreshed" in PostLayout; things do not move to the correct positions even though they have the correct positions and sizes assigned to them. I believe this is because PostLayout is true to its name, it is a callback that takes place AFTER the layout process. Putting code here seems hacky to me and doesn't even solve my problem, maybe I am using it wrong?
I have not seen any UGUI components that use dynamically instantiated components such as item renderers, so I don't know if this is something that UGUI currently supports or not. I just came from an AS3 position (with C++ before that) and I would love to see some in-depth documentation like the following:
Example AS3 Layout Documentation: http://help.adobe.com/en_US/flex/using/WS460ee381960520ad-2811830c121e9107ecb-7fff.html
There are two giant sections breaking down the exact lifecycle, what should be done in which functions, and also a breakdown on the steps needed to write your own custom component with additional links to even more supporting documentation.
I do know that UGUI is still very new, and am I very greatful that Unity is taking the time to put a foundation for a UI framework in. UGUI is a great start, and I hope to see its continual evolution as time comes! Thank you for your insight and any comments you may have.
Unfortunately I cannot post any code here, as it is proprietary to my company. I will be happy to provide as much additional information as needed, though! Upon completion, I would like to make the library accessible through the Unity Asset Store to help those that have complex UI woes.
You can delay its execution until the next frame so it receives the correct information
Update: I have been able to successfully delay the execution until the next frame. It does work at runtime, however in the editor it does not.
The editor only updates when the inspector changes. I implemented a workaround by implementing my own custom inspector that triggers an update call. That does work, however it only works when you have the UGUI GameObject in the hierarchy selected. This means when I am ending a play session and am returning to edit mode, the layouts do not size correctly and everything gets reset to the default sizes.
I haven't been able to find a way to get UGUI to update component sizes AND be able to do a layout in a single frame. The search continues... :)
Answer by RazaTech · Feb 14, 2018 at 10:54 AM
Hello ! This might give you confidence :P. you are right we have to wait for next frame for updated size.
what I did is. CommitData function as coroutine . when your data is set for example,
inputLabel.text = label; wait for one frame
Then do calculations and set position and size.
right now unfortunately there is no life cycle to implement.