EditorGUILayout.Foldout - no way to remember state?

This question has certainly been asked before, but it’s never gotten answered, and I’ve tried everything I can think of.

How can I get a EditorGUILayout.Foldout to remember its state when the object that has the script that it’s controlling has been deselected and then reselected?

If I initialize the boolean (foldout1) as true, the Foldout will always be open when I return to the object - if false, then it will always be folded up when I return.

@CustomEditor (test)
class testEditor extends Editor {
   var foldout1 : boolean = true;
   function OnInspectorGUI () {
       foldout1 = EditorGUILayout.Foldout(foldout1, "Something"); 
       if (foldout1) {
             target.something = EditorGUILayout.IntSlider ("Something",target.something, 0, 100);
      }
   }
}

Would love to see a working example, and I bet I’m not the only one.

What you can do is set the foldout to use the isExpanded property of one of the serializedObject properties you want to show inside the expanded area. this will work with any property type, it doesn’t have to be one that actually expands, it could be a text field…

So if you had

class MyTestClass : MonoBehaviour
{
string myTextField;
}

You could then have a custom editor for that like this

[CustomEditor(typeof(MyTestClass),true)]
public  class MyTestClassEditor : Editor 
{
public override void OnInspectorGUI(){
        SerializedProperty myTextField = serializedObject.FindProperty ("myTextField");
		myTextField.isExpanded = EditorGUILayout.Foldout (myTextField.isExpanded, "My Foldout");
            if(myTextField.isExpanded){
                   EditorGUILayout.PropertyField (myTextField);
             }
       }
}

Editors are created if an object of it’s type has been selected and are destroyed when the object gets deselected. If you want the editor to save the state individual for each object, you have to use a variable in the target script, which is not nice, since it blows up your class. However, you can surround the variable with:

#if UNITY_EDITOR 
[HideInInspector]
var foldout : boolean = true;
#endif

This way the variable only exists when you run your game in the editor. When you create a build it will not be there.

But the easiest way is to use a static variable in your editor. This will remember the state for the editor, not an individual object. If that’s what you want, static is what you need :wink:

class testEditor extends Editor
{
    static var foldout1 : boolean = true;
    [...]

As I can see, we are many to seek for a fold trick.

I did a little tool to fulfil the job.

using System.Collections.Generic;

public static class FoldRestorer
{
	private static Dictionary<int, bool>	folds = new Dictionary<int,bool>();

	public static bool	GetFold(int hash)
	{
		if (folds.ContainsKey(hash) == false)
		{
			folds[hash] = false;
			return false;
		}
		return folds[hash];
	}

	public static void	SetFold(int hash, bool value)
	{
		folds[hash] = value;
	}
}

After the tool, the example through a property drawer.

It is a little bit messy, but it is optimized.

		int	hash = property.propertyPath.GetHashCode();
		EditorGUI.BeginChangeCheck();
		bool	b = EditorGUI.Foldout(position, FoldRestorer.GetFold(hash), "Fold");
		if (EditorGUI.EndChangeCheck() == true)
			FoldRestorer.SetFold(hash, b);

It has been tested on Unity 4.3.

You can add a method into FoldRestorer to replace the block above and be more efficient.

Just a warning to anyone who implements this:

#if UNITY_EDITOR 
[HideInInspector]
var foldout : boolean = true;
#endif

This may be a ticking time bomb if you want your code to run on iOS, and possibly other platforms. It seems that Unity does not support having variables in the editor that do not appear in the runtime in every case. I tried using #if UNITY_EDITOR to compile out variables in the runtime version like this and it worked fine for Windows/Mac standalone builds, but when I moved it to iOS I got mysterious crashes during level load.

I’m not sure exactly what is going on, but I believe Unity is serializing those variables in the editor, but failing to deserialize them in the runtime (because they are compiled out) though the data is there in the input stream. This results in corrupted game object data and often leads to crashes.

It doesn’t always happen, but one situation where it does happen almost consistently is with an array of classes marked Serializable, something like this:

[System.Serializable]
class MyClass
{
    int var1;

#if UNITY_EDITOR
    int var2;
#endif
}

class MyComponent : MonoBehaviour
{
   public MyClass[] myItems;
}

I wrestled for most of a day with a crash bug caused by a similar bit of code. Removing the #if UNITY_EDITOR solved the problem for me, so I thought I’d share this in the hopes of saving someone else from the same fate.

Not sure if it’s the best way to do it, but I achieved this effect by saving the state using the EditorPrefs class in the OnEnable and OnDestroy methods of the custom editor script:

void OnDestroy()
{
	EditorPrefs.SetBool("FoldedOut", foldedout);
}
	
void OnEnable()
{
	foldedout = EditorPrefs.GetBool("FoldedOut", false);
}

With this approach the state will also be saved between Unity sessions.

Old question but I’d like to share my solution:

private bool Foldout(string name)
{
    string prefKey = target.GetInstanceID() + ".Foldout." + name;
    bool foldoutState = EditorPrefs.GetBool(prefKey, false);
    bool newFoldoutState = EditorGUILayout.Foldout(foldoutState, name);
    if (newFoldoutState != foldoutState)
        EditorPrefs.SetBool(prefKey, newFoldoutState);
    return newFoldoutState;
}

And use it like this:

if (Foldout("Configuration"))
{
...
}