# Apple code style

> SwiftFormat, SwiftLint, RuboCop, and Yamllint in the Apple project, and the coding conventions reviewers look for.

Source: https://contributors.home-assistant.io/companion/apple/code-style/

Last verified on 2026-10-14, against https://github.com/home-assistant/iOS/blob/main/.github/workflows/ci.yml.

The Apple project formats and lints its code automatically, so reviews can focus on behavior and edge cases instead of style.

## The tools

| Tool | Checks | Configured in |
| --- | --- | --- |
| [SwiftFormat](https://github.com/nicklockwood/SwiftFormat) | Swift formatting | `.swiftformat` |
| [SwiftLint](https://github.com/realm/SwiftLint) | Swift rules SwiftFormat doesn't cover, plus custom rules | `.swiftlint.yml` |
| [RuboCop](https://rubocop.org) | The Ruby code of Fastlane | `.rubocop.yml` |
| [Yamllint](https://yamllint.readthedocs.io/) | Workflows and other YAML | `.yamllint.yml` |

## Run them

```bash
# Report problems, change nothing
bundle exec fastlane lint

# Fix what can be fixed automatically
bundle exec fastlane autocorrect
```

Run `autocorrect` before every commit. Or let git do it for you:

```bash
bundle exec fastlane install_git_hooks
```

Inside Xcode, the linters only show warnings and don't change your code. CI also runs Yamllint, which is not part of the `lint` lane.

## What the configuration enforces

- **Formatting**: lines up to 120 characters, arguments and collections wrapped before the first element, `self` only in initializers, `guard` and `else` on the same line, and no file header comments.
- **SwiftLint** uses an allowlist of rules, not every default rule. Among them: no `force_cast` or `force_try`, and low cyclomatic complexity.
- **Custom SwiftLint rules**: never assign to `Current` outside tests, and use `SFSafeSymbols` instead of raw SF Symbol names.

```swift
// Wrong
Image(systemName: "house")

// Right
Image(systemSymbol: .house)
```

### Suppressing a rule

Be careful with `// swiftlint:disable`. CI looks for new ones in every pull request, and comments when it finds one (on pull requests from a branch in the repository itself), so a maintainer can check that it is really needed. Every suppression needs a good reason.

## Conventions

- **One type per file**, named after the type, and a `#Preview` for every SwiftUI view. See [rules for every change](https://contributors.home-assistant.io/companion/apple/architecture/#rules-for-every-change).
- **Log with `Current.Log`**, never `print` or `NSLog`.
- **Small, well-named types and functions** that are easy to test on their own.
- **Shared code goes in shared modules**, instead of being copied between targets. Don't put assumptions about one target into shared or extension code.
- **User facing text** goes in `en.lproj/Localizable.strings`. SwiftGen generates the `L10n` accessors you use in code, and translations come from [Lokalise](https://contributors.home-assistant.io/translate/).

## Generated files

Some files are generated by tools or workflows, like the `L10n` accessors and the icon and color sets. Generated files usually say so in a comment at the top. Don't edit them by hand: change the source and run the tool. When you touch Ruby or workflow code, make sure its linters still pass.
