Attribute drawer can only get properties which refer to array elements instead of array itself

Hello, I’m working on my ShowIf attribute.
I’m trying to use a PropertyDrawer to hide or show an property. It works well on single variables. But when it comes to an array, it looks like I’m dealing with every single element of the array instead of the array itself.

[CustomPropertyDrawer(typeof(MyShowIfAttribute))]
public class MyShowIfAttributeDrawer : PropertyDrawer {
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {}

The parameter property seems to refer to an element, not the array.

Here’s the definition of the attribute:

using UnityEngine;

public class MyShowIfAttribute : PropertyAttribute {
    
    public string ConditionFieldName { get; private set; }
    public bool ExpectedValue { get; private set; }

    public MyShowIfAttribute(string conditionFieldName, bool expectedValue = true)
    {
        ConditionFieldName = conditionFieldName;
        ExpectedValue = expectedValue;
    }
}

This is where I use the attribute. ComboKey is just an enum. I want to show inputKeys only when receiveDirection is true:

using System;
using System.Collections.Generic;

namespace ComboInput {
    [Serializable]
    public class Combo {
        public InputType inputType;

        [MyShowIf(nameof(receiveDirection))]
        public ComboKey[] inputKeys;
        public ComboKey inputKey;
        public bool receiveDirection;
        public bool needDirection;
        public ActionKey action;
        private int SimultaneousMask;
    }
}

When I try to get the property from OnGUI, I can only get every element.

Code of the Attribute Drawer
using System;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(MyShowIfAttribute))]
public class MyShowIfAttributeDrawer : PropertyDrawer {
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        MyShowIfAttribute showIf = (MyShowIfAttribute)attribute;
        string path = !property.propertyPath.Contains(".")
                ? showIf.ConditionFieldName
                : property.propertyPath.Contains("Array.data[") && property.propertyPath.EndsWith(']')
                    ? System.IO.Path.ChangeExtension(
                        property.propertyPath.Substring(0,
                            property.propertyPath.LastIndexOf("Array.data", StringComparison.Ordinal) - 1),
                        showIf.ConditionFieldName)
                    : System.IO.Path.ChangeExtension(property.propertyPath, showIf.ConditionFieldName)
            ;
        SerializedProperty conditionProperty = property.serializedObject.FindProperty(path);

        Debug.LogWarning("Property Path: " + property.propertyPath
                         + "\nYour Path: " + path
                         + "\nThis property is " + (property.isArray ? "" : "not ") + "array.");
        if (conditionProperty != null) {
            bool shouldShow = IsConditionMet(conditionProperty, showIf.ExpectedValue);
            if (shouldShow) {
                EditorGUI.PropertyField(position, property, label, true);
            }
        }
        else {
            Debug.LogWarning($"Cannot find property with name {showIf.ConditionFieldName}");
        }
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
        MyShowIfAttribute showIf = (MyShowIfAttribute)attribute;

        string path = !property.propertyPath.Contains(".")
                ? showIf.ConditionFieldName
                : property.propertyPath.Contains("Array.data[") && property.propertyPath.EndsWith(']')
                    ? System.IO.Path.ChangeExtension(
                        property.propertyPath.Substring(0,
                            property.propertyPath.LastIndexOf("Array.data", StringComparison.Ordinal) - 1),
                        showIf.ConditionFieldName)
                    : System.IO.Path.ChangeExtension(property.propertyPath, showIf.ConditionFieldName)
            ;
        SerializedProperty conditionProperty = property.serializedObject.FindProperty(path);
        if (conditionProperty != null && IsConditionMet(conditionProperty, showIf.ExpectedValue)) {
            return EditorGUI.GetPropertyHeight(property, label, true);
        }
        else {
            return 0f; // Hide property by setting height to 0
        }
    }

    private bool IsConditionMet(SerializedProperty conditionProperty, object expectedValue) {
        switch (conditionProperty.propertyType) {
            case SerializedPropertyType.Boolean:
                return conditionProperty.boolValue.Equals(expectedValue);
            default:
                Debug.LogWarning("Unsupported property type for ShowIf attribute.");
                return true;
        }
    }
}

The logs from line 20 look like these:


And the inspector looks like this (ComboList is List<Combo>):


The whole list is expected to hide but I can only hide every single element now.

What’s the problem? Is it possible to fix this?

Gee, it seems to be designed this way.

Note : Lists and arrays are handled differently with custom drawers. When the SerializedProperty is passed to the CreatePropertyGUI method, it represents each item in the list. However, when the custom drawing is needed for the list itself, you must wrap the property accordingly.

But the OnGUI method is not mentioned. And I want to figure out what does it mean to “wrap the property accordingly”.

I think wrapping the property accordingly is maybe use a wrapper class for your array data which will have a custom editor and you will have to draw the whole array by yourself (which means drawing button to add or remove element by yourself etc.)

Not directly related to the question but I just found out that the CustomDrawer object is the same instance for all array elements. Which means you can use data from previous element if you cache them in each OnGUI call (like use a dictionary with the SerializedProperty as a key, you can then iterate over all other elements from each element).

In Unity 6 you can use applyToCollection to make an attribute affect the whole array/list instead of each item.