Is it possible to group folders together at the top on Unity for MacOS? (Like on Windows)

Pretty straight forward…

**On Windows folders are sorted together at the top of the file list before their sibling files in the same folder.

I have found the Finder setting to accomplish this, which works for Finder, but it has no effect on how Unity operates**: Sort Folders and Files on Mac Just Like Windows With a Single Setting Switch

I also found a forum post from 2013 with an extension that is designed to fix this. However despite fixing the missing “&” symbols in the script, it still does not seem to work: https://forum.unity.com/threads/project-window-extension-script-folders-sorting-double-click-expand-collapse.180186/

I ALSO found a Unity Answers question on this from 2015, to which the first response came from me, today, 4.5 years later: https://answers.unity.com/questions/991484/project-view-folder-grouping-in-osx.html

REALLY hoping someone can help me out here because it’s driving me insane.

i’m also looking for the same thing. this folder-file sorting is killing my production speed.

Sorry to raise this post, but had to tweak some of the code to get it to work, thought I would share.

thank you @WeslomPo for the original!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Reflection;

[InitializeOnLoad]
public static class ProjectBrowserExtension
{
    private const string k_UnityEditorProjectBrowserAssemblyName = "UnityEditor.ProjectBrowser";
    private const string k_ProjectBrowsersFieldName = "s_ProjectBrowsers";
    private const string k_AssetTreeFieldName = "m_AssetTree";
    private const string k_ListAreaFieldName = "m_ListArea";
    private const string k_DataFieldName = "data";
    private const string k_FoldersFirstFieldName = "foldersFirst";

    private static readonly object s_BoolTrue = true;
    
    static ProjectBrowserExtension()
     {
         EditorApplication.projectChanged += OnChanged;
         EditorApplication.playModeStateChanged += OnPlayMode;
         EditorApplication.projectWindowItemOnGUI += OnFirstTime;
     }
     private static void OnFirstTime(string guid, Rect _)
     {
         EditorApplication.projectWindowItemOnGUI -= OnFirstTime;
         Refresh();
     }
     private static void OnChanged() => Refresh();
     private static void OnPlayMode(PlayModeStateChange obj) => Refresh();
     
     /// <summary>
     /// foreach browser in UnityEditor.ProjectBrowser.s_ProjectBrowsers
     ///     browser.m_AssetTree.data.foldersFirst = true
     ///     browser.m_ListArea.foldersFirst = true
     /// </summary>
     private static void Refresh()
     {
         Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.Editor));
         Type projectBrowser = assembly.GetType(k_UnityEditorProjectBrowserAssemblyName);
         FieldInfo field = projectBrowser.GetField(k_ProjectBrowsersFieldName, BindingFlags.Static | BindingFlags.NonPublic);
         if (field == null)
             return;
         IEnumerable list = (IEnumerable) field.GetValue(projectBrowser);
         foreach (object pb in list)
             SetFolderFirstForProjectWindow(pb);
     }
     private static void SetFolderFirstForProjectWindow(object pb)
     {
         IEnumerable<FieldInfo> members = pb.GetType().GetRuntimeFields();
         int maxMembersSought = 2;
         foreach (FieldInfo member in members)
         {
             switch (member.Name)
             {
                 // One column
                 case k_AssetTreeFieldName:
                     SetOneColumnFolderFirst(pb, member);
                     maxMembersSought--;
                     break;
                 // Two column
                 case k_ListAreaFieldName:
                     SetTwoColumnFolderFirst(pb, member);
                     maxMembersSought--;
                     break;
             }

             if (maxMembersSought == 0)
                 break;
         }
     }
     private static void SetTwoColumnFolderFirst(object pb, FieldInfo listAreaField)
     {
         if (listAreaField == null)
             return;
         object listArea = listAreaField.GetValue(pb);
         // safety check
         if (listArea == null)
             return;
         PropertyInfo folderFirst = listArea.GetType().GetProperties().Single(x => x.Name == k_FoldersFirstFieldName);
         folderFirst.SetValue(listArea, s_BoolTrue);
     }
     private static void SetOneColumnFolderFirst(object pb, FieldInfo assetTreeField)
     {
         if (assetTreeField == null)
             return;
         
         object assetTree = assetTreeField.GetValue(pb);
         // Fix: as we are looping all members, it's possible to end up in a case where one member is seen first, 
         // this will be null.
         if (assetTree == null)
             return;
         
         PropertyInfo data = assetTree.GetType().GetRuntimeProperties().Single(x => x.Name == k_DataFieldName);
         // AssetsTreeViewDataSource
         object dataSource = data.GetValue(assetTree);

         // safety check
         if (dataSource == null)
             return;
         PropertyInfo folderFirst = dataSource.GetType().GetProperties().Single(x => x.Name == k_FoldersFirstFieldName);
         folderFirst.SetValue(dataSource, s_BoolTrue);
     }
}

,Sorry to raise this post, but had to tweak some of the code to get it to work, thought I would share.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Reflection;

[InitializeOnLoad]
public static class ProjectBrowserExtension
{
    private const string k_UnityEditorProjectBrowserAssemblyName = "UnityEditor.ProjectBrowser";
    private const string k_ProjectBrowsersFieldName = "s_ProjectBrowsers";
    private const string k_AssetTreeFieldName = "m_AssetTree";
    private const string k_ListAreaFieldName = "m_ListArea";
    private const string k_DataFieldName = "data";
    private const string k_FoldersFirstFieldName = "foldersFirst";

    private static readonly object s_BoolTrue = true;
    
    static ProjectBrowserExtension()
     {
         EditorApplication.projectChanged += OnChanged;
         EditorApplication.playModeStateChanged += OnPlayMode;
         EditorApplication.projectWindowItemOnGUI += OnFirstTime;
     }
     private static void OnFirstTime(string guid, Rect _)
     {
         EditorApplication.projectWindowItemOnGUI -= OnFirstTime;
         Refresh();
     }
     private static void OnChanged() => Refresh();
     private static void OnPlayMode(PlayModeStateChange obj) => Refresh();
     
     /// <summary>
     /// foreach browser in UnityEditor.ProjectBrowser.s_ProjectBrowsers
     ///     browser.m_AssetTree.data.foldersFirst = true
     ///     browser.m_ListArea.foldersFirst = true
     /// </summary>
     private static void Refresh()
     {
         Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.Editor));
         Type projectBrowser = assembly.GetType(k_UnityEditorProjectBrowserAssemblyName);
         FieldInfo field = projectBrowser.GetField(k_ProjectBrowsersFieldName, BindingFlags.Static | BindingFlags.NonPublic);
         if (field == null)
             return;
         IEnumerable list = (IEnumerable) field.GetValue(projectBrowser);
         foreach (object pb in list)
             SetFolderFirstForProjectWindow(pb);
     }
     private static void SetFolderFirstForProjectWindow(object pb)
     {
         IEnumerable<FieldInfo> members = pb.GetType().GetRuntimeFields();
         int maxMembersSought = 2;
         foreach (FieldInfo member in members)
         {
             switch (member.Name)
             {
                 // One column
                 case k_AssetTreeFieldName:
                     SetOneColumnFolderFirst(pb, member);
                     maxMembersSought--;
                     break;
                 // Two column
                 case k_ListAreaFieldName:
                     SetTwoColumnFolderFirst(pb, member);
                     maxMembersSought--;
                     break;
             }

             if (maxMembersSought == 0)
                 break;
         }
     }
     private static void SetTwoColumnFolderFirst(object pb, FieldInfo listAreaField)
     {
         if (listAreaField == null)
             return;
         object listArea = listAreaField.GetValue(pb);
         // safety check
         if (listArea == null)
             return;
         PropertyInfo folderFirst = listArea.GetType().GetProperties().Single(x => x.Name == k_FoldersFirstFieldName);
         folderFirst.SetValue(listArea, s_BoolTrue);
     }
     private static void SetOneColumnFolderFirst(object pb, FieldInfo assetTreeField)
     {
         if (assetTreeField == null)
             return;
         
         object assetTree = assetTreeField.GetValue(pb);
         // Fix: as we are looping all members, it's possible to end up in a case where one member is seen first, 
         // this will be null.
         if (assetTree == null)
             return;
         
         PropertyInfo data = assetTree.GetType().GetRuntimeProperties().Single(x => x.Name == k_DataFieldName);
         // AssetsTreeViewDataSource
         object dataSource = data.GetValue(assetTree);

         // safety check
         if (dataSource == null)
             return;
         PropertyInfo folderFirst = dataSource.GetType().GetProperties().Single(x => x.Name == k_FoldersFirstFieldName);
         folderFirst.SetValue(dataSource, s_BoolTrue);
     }
}

It’s 2023 and we still don’t have a setting for this.

Hi @NMD83 ! How are you?

So, problem lay here


    // not actually code
    //        foreach browser in UnityEditor.ProjectBrowser.s_ProjectBrowsers
    //            browser.m_AssetTree.data.foldersFirst = true
    //            browser.m_ListArea.foldersFirst = true

    [InitializeOnLoad]
    public static class ProjectBrowserExtension {
        static ProjectBrowserExtension()
        {
            EditorApplication.projectChanged += OnChanged;
            EditorApplication.playModeStateChanged += OnPlayMode;
            EditorApplication.projectWindowItemOnGUI += OnFirstTime;
        }

        private static void OnFirstTime(string guid, Rect selectionrect)
        {
            EditorApplication.projectWindowItemOnGUI -= OnFirstTime;
            Refresh();
        }

        private static void OnChanged() => Refresh();

        private static void OnPlayMode(PlayModeStateChange obj) => Refresh();
        /// <summary>
        /// По сути, присваиваем значение определенной переменной:
        /// foreach browser in UnityEditor.ProjectBrowser.s_ProjectBrowsers
        ///     browser.m_AssetTree.data.foldersFirst = true
        ///     browser.m_ListArea.foldersFirst = true
        /// </summary>
        private static void Refresh()
        {
            var assembly = Assembly.GetAssembly(typeof(Editor));
            var projectBrowser = assembly.GetType("UnityEditor.ProjectBrowser");
            var field = projectBrowser.GetField("s_ProjectBrowsers", BindingFlags.Static | BindingFlags.NonPublic);

            if (field == null)
                return;

            var list = (IEnumerable) field.GetValue(projectBrowser);
            foreach (var pb in list)
                SetFolderFirstForProjectWindow(pb);
        }

        private static void SetFolderFirstForProjectWindow(object pb)
        {
            var members = pb.GetType().GetRuntimeFields();
            foreach (var member in members)
                switch (member.Name)
                {
                    // One column
                    case "m_AssetTree":
                        SetOneColumnFolderFirst(pb, member);
                        break;
                    // Two column
                    case "m_ListArea":
                        SetTwoColumnFolderFirst(pb, member);
                        break;
                }
        }

        private static void SetTwoColumnFolderFirst(object pb, FieldInfo listAreaField)
        {
            if (listAreaField == null)
                return;
            var listArea = listAreaField.GetValue(pb);
            var folderFirst = listArea.GetType().GetProperties().Single(x => x.Name == "foldersFirst");
            folderFirst.SetValue(listArea, true);
        }

        private static void SetOneColumnFolderFirst(object pb, FieldInfo assetTreeField)
        {
            if (assetTreeField == null)
                return;

            var assetTree = assetTreeField.GetValue(pb);
            var data = assetTree.GetType().GetRuntimeProperties().Single(x => x.Name == "data");
            // AssetsTreeViewDataSource
            var dataSource = data.GetValue(assetTree);
            var folderFirst = dataSource.GetType().GetProperties().Single(x => x.Name == "foldersFirst");
            folderFirst.SetValue(dataSource, true);
        }
    }

Check my answer here: https://forum.unity.com/threads/project-window-extension-script-folders-sorting-double-click-expand-collapse.180186/#post-8437088
199836-screenshot-2022-09-13-205650-1.png

"The script above was breaking every time anything reloaded and you’d need to click something inside the project folder for it to sort.
So instead i just directly modified UnityEngine.CoreModule.dll using dnSpy (running on windows 11 in parallels)

UnityEditor → ProjectBrowser → GetShouldShowFoldersFirst() → right click → Edit IL instructions → replace instructions with “ldc.i4.1”, “ret” (meaning ‘return true;’).
Save, replace the dll. Have windows style sorting forever without any scripts. Works with Unity 2021.3.9f1 for me."