A Deep-Dive Into Groups and Folder References
As mentioned above, there are lots of answers here on SO around this topic, all which explain the basic differences. However, there are some subtle details that are omitted when it comes to behaviors and understanding of what's actually going on which can lead to a frustrating experience when your project or its changes aren't working as expected. As such, I'm tossing my hat into the ring with a more thorough explanation. Hopefully this will help avoid a lot of headaches.
Now, let's get our hands dirty!
Folder References
Folder references are the easier of the two to grasp. They are simple pointers to actual, on-disk folders. A folder reference can be identified by its blue icon.
Folder references track additions, renames and deletions to their referenced folder's contents, automatically updating the project tree as needed, even if those changes are made from outside of Xcode.
Prior to Xcode 16 (see note below), the main purpose of a folder reference was to include its contents as bundled resources for any selected targets.
Important things to note about targeting a folder reference:
You can only choose the target at the folder reference level, not the individual file or subfolder level. It's an all-or-nothing addition.
Any source code files in the folder reference are simply added to the bundle as file resources, just like any other file. Prior to Xcode 16, they cannot be compiled from within a folder reference.
📂🔨 Update for Xcode 16 - Folder References Now Support Code Compilation 📂🔨
As of Xcode 16, folder references now have an option to compile their contents via a new 'Build Folder Contents' in the inspector. When checked, all files in the referenced folder or any of its subfolders are excluded from resource copying, and those which are source files are implicitly added to the 'Compile Sources' build phase for the current target. You can change or remove targets for individual files using the inspector, just like with groups. It does this by recording an exception for that file in the project file (as opposed to an 'inclusion' like with groups).
Another benefit is unlike groups, folder references always track changes to the physical folder hierarchy beneath them. This means simply adding a new source file to that folder will auto-include it for compilation in any targets the folder reference belongs to.* Same thing with deleting a file from disk.*
Another benefit is the Xcode project tree now auto-sorts the files and folders within a folder reference for you. No more manually reordering them.
By far however, the biggest benefit is since membership in the project is now dictated by the physical contents on-disk*, additions, deletions and renames no longer require modifying the project file, meaning no more project-file conflicts during merges!! 🎉🎉🎉 (This alone was reason for us to convert all our projects to this new format.)
(* Note: You do have the ability to override this on a per-item basis, but the project file will only record those exceptions.) Additionally, since the project navigator is tracking at the folder level, not the individual file level, it always reflects exactly what's on disk, and always sorts the contents of each folder alphabetically by filename.
Groups
Groups on the other hand are deceivingly more complex. In their simplest explanation, they are logical-only containers in your Xcode project that you use to store and organize relative pointers to your source code files. These are stored in the Xcode project only, not on disk.
You can identify a group in your project by a yellow folder.
Each item, whether a group itself or a file-reference within the group specifies a location and how it is related to the project's structure. This can be found in the 'Identity and Type' section of the inspector.
Of note...
- Groups can reference a specific folder on disk, but don't have to
- File references must point to a specific file on disk.
How those references are stored depends on the value for 'Location':
- If 'Location' is set to 'Absolute Path' then the reference will contain the entire on-disk path to the item.
- If it's set to 'Relative to group', then its first determined what the group currently resolves to, then the references are stored relative to that value. This will propagate recursively up through the parent groups.
There's also a subtle difference in the iconography. If the group points to an actual folder, you will just see the yellow folder. If however, the group doesn't reference a specific folder (i.e. it is logical-only) you will see a small triangle in the folder's icon.
Additionally, regardless of whether the group is connected or not, you can always see what the current physical location referenced by a group or any of its children by looking at the 'Full Path' property in your inspector.
Again, it's important to note that your logical 'groups' structure has nothing to do with the physical on-disk structure. People regularly make the mistake of thinking starting with Xcode 9, that statement isn't true, but read on to know why that misconception still exists.
As mentioned, groups do not represent actual on-disk folders. They are simple logical groupings purely for code organization. However, they can point to an actual on-disk folder. You set which folder that is via the little folder button under the Location dropdown in the 'Identity and Type' section of the inspector. You can also clear that association again by pressing the little x in the circle.
Now here's the tricky part to get. When dealing with groups that aren't pointing to actual folders, their entire purpose is simply to help you as a user organize your code in a structure that makes sense to you, and for Xcode to be able to resolve the actual paths on disk to those file references.
But what if you have a group that does point to a folder? Well now an entire new set of rules comes into play. These rules are what changed in Xcode 9.
After Xcode 9, modifying a group's name may modify the on-disk name, but not necessarily. It will only do so if the group name and physical folder name match prior to the rename. If they don't, they are for all intents and purposes 'disconnected'.
For instance, say you have a group in your project called 'Virtual' that points to a folder on-disk called Literal, like so...
Group On-disk Folder ----- -------------- Virtual -> Literal
If you rename the group 'Virtual' to 'Conceptual', nothing happens on-disk.
Conceptual -> Literal
If you then rename 'Conceptual' to 'Literal' so it matches the actual folder name on disk...
Literal -> Literal
...then again rename 'Literal' to 'Changed', both the group and the folder will be updated to 'Changed'.
Changed -> Changed
Note: This has nothing to do with where the folder is on-disk. This is only pertaining to the name itself, nothing else.
Where are we going?
As for where it is on disk, there too things are more complex than they seem. If you move a group that is not currently pointing to an actual on-disk folder, nothing happens except Xcode updates how it stores your project's items' relative paths in the project file, making them relative to the updated group structure.
However if you move a group that is currently pointing to a folder--even if its name doesn't match the folder on disk (that is a critical point and an oft-source of confusion about 'corrupted' project trees)--the physical on-disk folder it points to will be moved to the new location relative to whatever group you drag it under, along with all items in that folder on disk whether referenced in your project or not!
For instance, say you have this in your project structure...
Project GroupA -> Points to \Code\Project\GroupA GroupB -> Points to \Some\Really\Deep\Path\Somewhere\Else\On\The\Disk\Entirely\GroupB
And you drag GroupA so it's under GroupB in your project tree tree, like so...
Project GroupB GroupA
Since GroupA points to a physical folder on-disk (again, remember, this has nothing to do with whether the group name and directory name match, only that the group points to an actual directory), the on-disk directory and all of its contents whether referenced or not will actually physically move to
\Some\Really\Deep\Path\Somewhere\Else\On\The\Disk\Entirely\GroupB\GroupA
Again, you are moving it to be under whatever the target group's actual on-disk path is.
Now, if GroupB doesn't actually point to a folder, but GroupA does, then the final location resolves to where GroupB resolves to on disk, which means it considers all of its parent groups to determine the location.
Most Common Scenario
The good news is that for 95% of all use-cases--really, unless you are dealing with a legacy code-base or you actively alter a new project's structure--the physical on-disk folder structure will match the project structure, and all those folder references should be set 'Relative to Group' meaning the groups are essentially mirroring the file system. That is why people think starting with Xcode 9, you are modifying the file system, but again, you are not. You're still just modifying groups and file references. It's just Xcode is making assumptions that if your groups structure mirrors the physical disk and you rename/reorder them, chances are you also want the physical disk to update, so it does it for you. It's very convenient if albeit a little misleading.
With this new knowledge, if things get out of whack and aren't working as you're expecting them to, it may be time to check your file and group references and relative locations. Make sure your folder references are actually pointing to where you are expecting them to be, especially when upgrading older projects, and you should be able to get back on track pretty quickly.
Anyway, hope this better explains things. Sure helped us! :)