How to edit array/list property with custom PropertyDrawer?
This is not about custom inspectors for arrays/lists in general (I assume/hope that I already know how to do that). I am having a specifically weird behaviour only when using arrays/lists as nested properties.
In brief: Using EditorGUI.PropertyField on a SerializedProperty (which is actually a string[]) won't display a proper array/list inspector element. Instead, I am only seeing a dull "foldout icon".
And In details...
I am using a custom PropertyDrawer (see source code below) for handling a simple custom data class instance inside the Inspector.
My approach seems to be very simple, mainly follwowing the Unity docs at: https://docs.unity3d.com/ScriptReference/PropertyDrawer.html
Here's my"data item class":
 [Serializable]
 public class ThesaurusItem
     {
         public string word;
         public string[] syllables;
     }
 
               The issue goes with its "syllables" property (string[]). The data class is used as a plain and straight property inside the surrounding MonoBehaviour class:
     public class Thesaurus : MonoBehaviour
     {
         public ThesaurusItem testItem;
     }
 
               Here's my issue: Any array or list type properties like "syllables" are only displayed as a "foldout" icon - and otherwise emtpy. In particular, there's no "size" input field at all.
This is what I'd expect to see in the Inspector (which is what I get for a simple public string[] class property): 
And here's what I actually get: 
As soon as my string[] property is handled as part of the data item class by the custom drawer, it is nothing more than a "foldout" icon - with nothing else otherwise. I can click it (i.e. it turns into a downward arrow), but it doesn't expand anything.
Of course my array property is still empty, so I don't expect to see any content. But I would expect to see at least the "size" field.
Finally, here's my PropertyDrawer:
     // Note that this is more or less identical to Unity's example code here: https://docs.unity3d.com/ScriptReference/PropertyDrawer.html
     [CustomPropertyDrawer(typeof(ThesaurusItem))]
     public class ThesaurusItemDrawer : PropertyDrawer
     {
         // Draw the property inside the given rect
         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
         {
             // Using BeginProperty / EndProperty on the parent property means that
             // prefab override logic works on the entire property.
             EditorGUI.BeginProperty(position, label, property);
 
             // Draw label
             position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
 
             // Don't make child fields be indented
             var indent = EditorGUI.indentLevel;
             EditorGUI.indentLevel = 0;
 
             // Calculate rects
             Rect wordRect = new Rectposition.x, position.y, 90, position.height);
             Rect syllablesRect = new Rect(wordRect.position.x + wordRect.width + 20, position.y, 50, position.height);
 
             // Draw fields - pass GUIContent.none to each so they are drawn without labels
             EditorGUI.PropertyField(wordRect, property.FindPropertyRelative("word"), GUIContent.none);
             EditorGUI.PropertyField(syllablesRect, property.FindPropertyRelative("syllables"), GUIContent.none, true);
 
             // Set indent back to what it was
             EditorGUI.indentLevel = indent;
 
             EditorGUI.EndProperty();
         }
     }
     
 
               Any ideas what's going wrong here?
Answer by coffiarts · Jan 04, 2019 at 08:38 AM
I've found a workaround for my own (admittedly quite lenghty and complicated) question, so I'd like to share it myself.
First of all: Unity doesn't seem to support custom drawers for arrays and lists themselves. See this older post from a Unity staff member Literally:
You can't make a PropertyDrawer for arrays or generic lists themselves. [...] On the plus side, elements inside arrays and lists do work with PropertyDrawers.
The solution sounds simple: Wrapping arrays/lists in a parent class and creating a drawer for the parent.
This works fine (in terms that the drawer will actually be applied to the array/list). The downside is that the individual elements of the array/list won't be automatically rendered, neither the "size" field. You still have to implement this manually (by looping etc.). Especially the handling of the array size looks a bit nasty, as you need to track the size in a separate int property (see "arrSize" below) and listen to changes to it.
My solution looks like this now:
Custom Wrapper class plus Drawer for a plain string[] property:
 // Encapsulation of a plain string[] inside a Property, so that it can be used together with a custom PropertyDrawer (StringArrayAttributeDrawer)
     [Serializable]
     public class StringArrayEditorAttribute : PropertyAttribute
     {
         public static readonly int INITIAL_SIZE = 1;
         public string[] values;
 
         public StringArrayEditorAttribute()
         {
             values = new string[INITIAL_SIZE];
             for (int i = 0; i < INITIAL_SIZE; i++)
             {
                 values[i] = "";
             }
         }
     }
 [CustomPropertyDrawer(typeof(StringArrayEditorAttribute))]
 public class StringArrayEditorAttributeDrawer : PropertyDrawer
 {
     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
     {
         SerializedProperty arrayProp = property.FindPropertyRelative("values");
         for (int i = 0; i < arrayProp.arraySize; i++)
         {
              // This will display an Inspector Field for each array item (layout this as desired)
             SerializedProperty value = arrayProp.GetArrayElementAtIndex(i);
             EditorGUI.PropertyField(position, value, GUIContent.none);
         }
     }
 }
 
               Use this property inside your surrounding class instead of a string[]:
    [Serializable]
     public class MyListItem : PropertyAttribute
     {
         public StringArrayEditorAttribute stringArrProp =  new StringArrayEditorAttribute();
         public int arrSize; // necessary for handling changes to the array's size!
     }
 
               Inside the Drawer for the surrounding class, it's sufficient to pass the property into a regular PropertyField
  [CustomPropertyDrawer(typeof(ThesaurusItem))]
  public class ThesaurusItemDrawer : PropertyDrawer
  {
             [...]
             EditorGUI.LabelField(someRect, new GUIContent("size"));
             [...]
             EditorGUI.PropertyField(someRect, property.FindPropertyRelative("arrSize"), GUIContent.none);
              [...]
             EditorGUI.PropertyField(someRect, property.FindPropertyRelative("stringArrProp"), GUIContent.none);
 }
 
 
               Now for the ugly part: Keeping track of array size changes...
I found no better way for this than using OnValidate() in the surrounding (i.e. the inspected) MonoBehaviour component (credits to user Edy for his solution proposed here): whenever anything changes in the inspector, we need to check any of the array items for a change in arrSize, and then adjust the array length accordingly (very nasty, but it works!):
 public class MyComponent : MonoBehaviour
 {
     OnValidate()
     {
             foreach (MyListItem item in someParentList)
             {
                 // Do we need to adjust the array to a new size?
                 if (item.arrSize != item.stringArrProp.values.Length)
                 {
                     string[] newArray = new string[item.arrSize];
                     for (int i = 0; i < newArray.Length; i++)
                     {
                         if (item.stringArrProp.values.Length > i)
                         {
                             newArray[i] = item.stringArrProp.values[i];
                         }
                         else
                         {
                             newArray[i] = "";
                         }
                     }
                     item.stringArrProp.values = newArray;
                 }
     }
     }
 }
 
              Answer by ShawnFeatherly · Aug 15, 2019 at 12:26 AM
Similar to @coffiarts 's answer, here's a pretty clean example of wrapping an array type, such as string[], in another class. https://github.com/cfoulston/Unity-Reorderable-List/blob/master/Example/Example.cs From what I can tell, he found a way around the array size check requirement being in OnValidate().
Answer by tomekkie · Aug 04, 2021 at 06:17 AM
Looks like this is not a problem in the recent versions of Unity any more. I have run into this issue after importing the CustomPropertyDrawer from 2020.2.2f1 - where it is working properly on lists or arrays without any custom wrapper - to 2020.1.0f1 - where it is not working.
Your answer
 
             Follow this Question
Related Questions
Popup to choose which child class I want in Custom Editor Inspector 0 Answers
Property Drawer ArgumentException 1 Answer
Is there a way to live-update script-controlled UI formatting in the editor? 0 Answers
Displaying recursive types in inspector (Custom Editor) 0 Answers
Custom Inspector for Particle System 2 Answers