Search Issue Tracker

Fixed in 2019.3.X

Fixed in 2019.1.X, 2019.2.X

Votes

28

Found in

2019.1.0a5

2019.1.0f2

Issue ID

1146883

Regression

Yes

Re-enabling game object with "Toggle group" loses information about previously checked toggle

uGUI

-

Steps to reproduce:
1. Open attached project "Test.zip"
2. Go into play mode
3. There are 3 toggles at the top, select a red one (row of red toggles should appear on the left side)
4. Check the first toggle on the left side row (You can check any toggle, but remember your choice)
5. Press green toggle (from 3 toggles at the top)
6. Press red toggle again (from 3 toggles at the top)
-The first toggle is not selected anymore from the left row, instead, another toggle is checked randomly. (Attached a video)

Explanation: Toggles at the top disable/enable "Toggle group" (only a single game object with toggle group is active at a time)

Expected result: After re-enabling element with "Toggle group" it shouldn't uncheck previously checked toggle
Actual result: Re-enabling game object with "Toggle group" loses information about previously checked toggle and instead checks random toggle in the group

Note: Sometimes it takes a couple of tries to reproduce the bug (repeat steps from 3rd step)

Reproduced on: 2019.1.0a5, 2019.1.0a6, 2019.1.0a11, 2019.1.0f2, 2019.2.0a1, 2019.2.0a10
Doesn't reproduce on: 2017.4.0f1, 2018.3.13f1, 2019.1.0a1, 2019.1.0a3, 2019.1.0a4
Regressed in: 2019.1.0a5

  1. Resolution Note (fix version 2019.3):

    By default, ToggleGroup is coded to activate/deactivate the container for all associated Toggle elements. When the container is deactivated, all contained Toggles will get their OnDisable called. This will cause the Toggle to unregister itself from the ToggleGroup.

    When a Toggle calls UnregisterToggle() on it's ToggleGroup, it is removed from the ToggleGroup's internal list.

    Previously, any time UnregisterToggle() was called on a ToggleGroup that is set to disallow an off state, the group will then scan its list for at least one checked Toggle. Because the checked toggle is ultimately removed from the list before the list is totally emptied, the group will attempt to fix itself and set the first Toggle in the list to be On. This creates unpredictable behavior, both because the order of the list is non-deterministic, and because the first list element is rarely the value that should be set to On.

    This PR moves the logic for ensuring that one Toggle is marked On to a new function. The only times that this function actually needs to be invoked are:

    a) when the ToggleGroup gets its Start() method invoked. This will ensure that a ToggleGroup created in the Editor has a valid state when loaded.
    b) when a Toggle is deleted from the ToggleGroup. If the checked toggle is deleted programmatically or via the scene hierarchy, the list must choose a new one.

    New Toggles that are added programmatically will fix the state of the ToggleGroup as happened previously.

Comments (55)

  1. rushk1

    Jun 06, 2019 09:43

    Is this issue still being worked on ? It says fixed and yet it doesn't seem to be.

  2. Nananaaa

    Jun 05, 2019 08:36

    Well, that one's a showstopper.
    Back to 2018.4 then...

  3. DreamKnight

    Jun 05, 2019 06:05

    reoccur problem in 2019.3.0a4.

    Allow Switch off + Active ON/OFF...
    Selection is move to Last TAB...

  4. DreamKnight

    Jun 05, 2019 05:56

    reoccur problem in 2019.3.0a4.

  5. Maulwurfmann

    Jun 04, 2019 09:07

    Just to save to time testing: Still not fixed in Unity 2019.1.5 :/

  6. eladleb4

    Jun 02, 2019 06:32

    This was stopping me from updating to 2019 so I created this small helper MonoBehaviour script that resolved the "forgetting" issue.

    I added to every gameobject that has a ToggleGroup. It should help stop "forgetting" which toggle was selected when it becomes enabled again.
    Hope this helps others.

    -=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=-
    ToggleGroupHelper.cs
    -=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=-

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;

    public class ToggleGroupHelper : MonoBehaviour
    {
    private Toggle rememberMe;

    private void OnEnable()
    {
    if (rememberMe != null)
    {
    rememberMe.isOn = true;
    rememberMe.onValueChanged.Invoke(true);
    }
    }

    private void OnDisable()
    {
    List<Toggle> active = new List<Toggle>(this.GetComponentsInChildren<Toggle>(true));
    this.rememberMe = active.Find(t => t.isOn);
    }
    }

  7. Saulius

    May 31, 2019 21:34

    Doesn't seem to be fixed in 2019.1.4f. I still get the same behavior. Very frustrating...

  8. Silvers

    May 31, 2019 13:44

    Would really appreciate this being fixed in 2019.2 instead of .3

  9. pjj_gpu

    May 23, 2019 22:51

    For anybody in a rush for a temporary solution this might help. Here is a version of ToggleGroup based on Unity's source on GitHub.... My change is: it searches for Toggle components in children at Start() and doesn't set (or rely on) the Group reference in each Toggle. Which means when a Toggle is disabled it DOESN"T call the usual group code that does the unwanted stuff. No guarantees .... it might work for you ...great. It is named differently so you would have to replace the standard ToggleGroup with it.

    using System;
    using System.Linq;
    using System.Collections.Generic;
    using UnityEngine.EventSystems;

    namespace UnityEngine.UI
    {
    //x[AddComponentMenu("UI/Toggle Group", 32)]
    [DisallowMultipleComponent]
    public class ToggleGroupPJJ : UIBehaviour
    {
    [SerializeField] private bool m_AllowSwitchOff = false;
    public bool allowSwitchOff { get { return m_AllowSwitchOff; } set { m_AllowSwitchOff = value; } }

    private List<Toggle> m_Toggles; //x = new List<Toggle>();

    protected ToggleGroupPJJ()
    {}

    private void f_validateToggleIsInGroup(Toggle toggle)
    {
    if (toggle == null || !m_Toggles.Contains(toggle))
    throw new ArgumentException(string.Format("Toggle {0} is not part of ToggleGroup {1}", new object[] {toggle, this}));
    }

    public void NotifyToggleOn(Toggle toggle)
    {
    f_initialize();
    f_validateToggleIsInGroup(toggle);

    // disable all toggles in the group
    for (var i = 0; i < m_Toggles.Count; i++)
    {
    if (m_Toggles[i] == toggle)
    continue;

    m_Toggles[i].isOn = false;
    }
    }

    public void UnregisterToggle(Toggle toggle)
    {
    if (m_Toggles.Contains(toggle))
    m_Toggles.Remove(toggle);
    }

    public void RegisterToggle(Toggle toggle)
    {
    if (!m_Toggles.Contains(toggle))
    m_Toggles.Add(toggle);
    }

    public bool AnyTogglesOn()
    {
    f_initialize();
    return m_Toggles.Find(x => x.isOn) != null;
    }

    public IEnumerable<Toggle> ActiveToggles()
    {
    f_initialize();
    return m_Toggles.Where(x => x.isOn);
    }

    public List<Toggle> AllToggles()
    {
    f_initialize();
    return m_Toggles;
    }

    public Toggle FirstActiveToggles()
    {
    f_initialize();
    return m_Toggles.Where(x => x.isOn).FirstOrDefault();
    }

    public void SetAllTogglesOff()
    {
    f_initialize();
    bool oldAllowSwitchOff = m_AllowSwitchOff;
    m_AllowSwitchOff = true;

    for (var i = 0; i < m_Toggles.Count; i++)
    m_Toggles[i].isOn = false;

    m_AllowSwitchOff = oldAllowSwitchOff;
    }

    List<bool> m_WhatChanged;

    public void onToggle(bool b)
    {
    int ind = 0;
    if (b == true)
    {
    int changedInd = -1;
    ind = 0;
    foreach( var tog in m_Toggles )
    {
    if (tog.isOn != m_WhatChanged[ind])
    changedInd = ind;
    ind++;
    }
    if( changedInd != -1 )
    {
    bool priorDisableState = UIMapping.m_DiableUICallback;
    UIMapping.m_DiableUICallback = true;
    ind = 0;
    foreach( var tog in m_Toggles )
    {
    if (tog.isOn && ind != changedInd )
    {
    tog.isOn = false;
    }
    m_WhatChanged[ind] = tog.isOn;
    ind++;
    }
    UIMapping.m_DiableUICallback = priorDisableState;

    }

    }
    ind = 0;
    foreach (var tog in m_Toggles)
    {
    m_WhatChanged[ind] = tog.isOn;
    ind++;
    }

    }

    void f_initialize()
    {
    if( m_Toggles==null )
    {
    m_Toggles = transform.GetComponentsInChildren<Toggle>().ToList();
    m_WhatChanged = new List<bool>();
    foreach( var tog in m_Toggles )
    {
    tog.onValueChanged.AddListener(onToggle);
    m_WhatChanged.Add(tog.isOn);
    }
    }
    }
    public void ReInitialize()
    {
    m_Toggles = null;
    f_initialize();
    }
    //!? Hack if dynamically created toogles
    //!? aren't created by 'Start' time then they won't be found
    protected override void Start()
    {
    base.Start();
    f_initialize();

    }
    }
    }

  10. pjj_gpu

    May 23, 2019 22:18

    The same but slightly. different Additionally It was inconvenient to get a 'UI Toggle Event' when disabling a panel with a toggle group. I don't regard disabling a panel with groups the same as clicking a toggle.

Add comment

Log in to post comment