How to Use Xcode Swift-Format to Enforce Code Style Guidelines

No imageSilviu V.
73 Apr, 2025

Consistent code style is key for readability and teamwork. Xcode’s Swift-Format helps enforce coding guidelines automatically. In this guide, I’ll show you how to set it up and customize it to keep your project clean and consistent with minimal effort. 🔧

Up until now, and even moving forward, many of us iOS developers have relied on third-party packages like:

  • SwiftLint
  • Tailor, or
  • ClangFormat-Xcode

to enforce Swift/Obj-C code style guidelines. While these tools have been helpful, it's surprising to see that other IDEs like IntelliJ IDEA, Visual Studio Code and others have had advanced code formatting features for years. In contrast, Xcode has only offered basic indentation formatting with Ctrl + i, which is far from a full-fledged formatting solution. 😩

Xcode 16 introduces a more advanced code formatting feature!

No Image
Shortcut: ctrl(^) + shift + i

If you run that shortcut or use the Swift-Format option, you'll notice it does much more than just aligning rows and adding basic indentation. It removes semicolons, eliminates unnecessary spaces between keywords, enforces specific line lengths, and applies many other formatting rules to keep your code clean and consistent.

For example, consider this messy code: 😵‍💫

No Image

After applying Swift-Format, here’s the amazing result: ✅

No Image

It did the following:

  • Removed semicolons (which are not needed in Swift)
  • Reduced empty lines to a maximum of one space between code lines
  • Fixed indentation

These are just some of the basic formatting options. There are also more advanced rules that you can tweak to further customize the formatting to your preferences.


Now you might be wondering, how does Swift-Format know which guidelines to follow? Surely, it must have a configuration somewhere, and you'd be right!

To get started, open a terminal and type the following command to install the swift-format tool:

bash
brew install swift-format

Once the installation is complete, you can check the default configuration that XCode uses when you format your code, by typing:

bash
swift-format dump-configuration

Result:

No Image

These key-value pairs determine how the formatter will modify the code. There are many rules, and you don't need to memorize them. You can always refer to the official documentation 📝 to search for specific rules and learn what they do and how they affect your code.

What I love most about this is that you don't have to rely on the default configuration at all. You can create your own custom configuration for each project, allowing different rules and styles across multiple projects and teams.

This means you and your team can decide together what rules and styles you want to enforce in your project.

Basically, when you apply this format, the formatter will search the current directory for a configuration file. If it can't find one, it will look up the directory tree until it reaches the root of the project, where you typically want to place the configuration file. If it still can't find it, it will fall back on the default configuration I showed you earlier.

No Image
Be sure to prefix the file name with a dot (.)

Next, open the file and paste the default configuration, which you can tweak later. I’ll share the configuration that worked best for our projects. As I mentioned, there’s no one-size-fits-all rule, so feel free to adjust it as needed.

json
{ "fileScopedDeclarationPrivacy": { "accessLevel": "private" }, "indentConditionalCompilationBlocks": true, "indentSwitchCaseLabels": false, "indentation": { "spaces": 4 }, "lineBreakAroundMultilineExpressionChainComponents": true, "lineBreakBeforeControlFlowKeywords": false, "lineBreakBeforeEachArgument": true, "lineBreakBeforeEachGenericRequirement": true, "lineLength": 120, "maximumBlankLines": 1, "multiElementCollectionTrailingCommas": true, "noAssignmentInExpressions": { "allowedFunctions": ["XCTAssertNoThrow"] }, "prioritizeKeepingFunctionOutputTogether": false, "respectsExistingLineBreaks": true, "rules": { "AllPublicDeclarationsHaveDocumentation": false, "AlwaysUseLiteralForEmptyCollectionInit": true, "AlwaysUseLowerCamelCase": true, "AmbiguousTrailingClosureOverload": true, "BeginDocumentationCommentWithOneLineSummary": true, "DoNotUseSemicolons": true, "DontRepeatTypeInStaticProperties": true, "FileScopedDeclarationPrivacy": true, "FullyIndirectEnum": true, "GroupNumericLiterals": true, "IdentifiersMustBeASCII": true, "NeverForceUnwrap": false, "NeverUseForceTry": false, "NeverUseImplicitlyUnwrappedOptionals": true, "NoAccessLevelOnExtensionDeclaration": true, "NoAssignmentInExpressions": true, "NoBlockComments": true, "NoCasesWithOnlyFallthrough": true, "NoEmptyTrailingClosureParentheses": true, "NoLabelsInCasePatterns": true, "NoLeadingUnderscores": true, "NoParensAroundConditions": true, "NoPlaygroundLiterals": true, "NoVoidReturnOnFunctionSignature": true, "OmitExplicitReturns": true, "OneCasePerLine": true, "OneVariableDeclarationPerLine": true, "OnlyOneTrailingClosureArgument": true, "OrderedImports": true, "ReplaceForEachWithForLoop": false, "ReturnVoidInsteadOfEmptyTuple": true, "TypeNamesShouldBeCapitalized": true, "UseEarlyExits": false, "UseExplicitNilCheckInConditions": true, "UseLetInEveryBoundCaseVariable": true, "UseShorthandTypeNames": true, "UseSingleLinePropertyGetter": true, "UseSynthesizedInitializer": true, "UseTripleSlashForDocumentationComments": true, "UseWhereClausesInForLoops": true, "ValidateDocumentationComments": true }, "spacesAroundRangeFormationOperators": true, "tabWidth": 2, "version": 1 }

Relying on all team members to use that shortcut to format their code and ensure the remote code is always clean and neat isn't an efficient approach, is it? 🙄

That’s why I found it useful to have a GitHub Action set up in the repository. Whenever a merge or push is made, the action can run a script that checks all the Swift files and formats them according to the root project configuration file before the push is completed.

If that's not an option, I found it really useful to set up a Git hook locally.

Essentially, you create a new folder called hooks inside the hidden .git directory. Then, create an executable file named pre-commit inside that folder. After that, open the terminal in the hooks directory and run the following command to make the file executable:

bash
chmod +x pre-commit

Then, open the pre-commit file with your favorite text editor and paste this simple script:

bash
#!/bin/sh echo "Pre-commit hook started." # Check if swift-format is installed if ! command -v swift-format &> /dev/null; then echo "❌ Error: swift-format is not installed. Install it using:" echo " brew install swift-format" exit 1 fi # Set IFS to handle spaces in file names correctly IFS=$'\n' # Get a list of staged Swift files staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.swift$') # Check if there are any staged Swift files if [ -z "$staged_files" ]; then echo "No staged Swift files to format." echo "Pre-commit hook completed." exit 0 fi echo "Staged Swift files to be formatted:" for file in $staged_files; do echo " - $file" done # Format each staged Swift file for file in $staged_files; do if [ -f "$file" ]; then echo "Formatting '$file'..." swift-format format -i "$file" git add "$file" echo "'$file' has been formatted and re-staged." else echo "Warning: '$file' does not exist." fi done echo "Pre-commit hook completed." exit 0

Here’s what it does:

  • Checks if swift-format is installed: If not, it prompts the user to install it.

  • Gets a list of staged Swift files: Filters out only .swift files that are staged for commit.

  • Formats each staged Swift file: Runs swift-format on each file and re-stages them after formatting.

  • Displays formatted files: Outputs the names of the files being formatted and confirms when they've been re-staged.

  • Exits cleanly: If no .swift files are staged or after the formatting process, it finishes with a success message.

What’s great about this is that the script runs before a commit is made, ensuring that your Git history remains clean and only shows properly formatted code. You no longer have to worry about formatting your code manually. 🎉


Thanks for stopping by! 🪴