Introduction
We have added linting for the Kotlin Android application, but that is only half of the KMM architecture. The other half, Native Swift (Swift), is missing out on all the benefits of Danger
.
In this article, we will explore static code analysis on iOS, covering local linting, integrating linting into a Continuous Integration (CI) flow using GitHub Actions, and finally, generating and publishing a summary report for each Pull Request.
Why lint
💡 TL;DR: Lint is an efficient way to ensure code quality.
Read more about why linting in the first part “Linting KMM Android Application with Danger”.
Lint with SwiftLint
SwiftLint
is a Swift linter tool for Swift that enforces iOS coding standards automatically.
There are multiple ways to install SwiftLint
. The recommended way is via CocoaPods
because:
- KMM project uses
CocoaPods
to connect the Native application to the Kotlin modules. - Keep
SwiftLint
dependent on the project.
Add the following pod to the Podfile
, then run pod install
in the iosApp directory.
target 'iosApp' do
# Other pods
# ...
pod 'SwiftLint'
# ...
end
SwiftLint is run via Pods/SwiftLint/SwiftLint
inside the iosApp
folder or iosApp/Pods/SwiftLint/SwiftLint
from the root folder.
Configure SwiftLint Rules
SwiftLint
allows rule configuration via .SwiftLint.yml
. Here is an example configuration.
excluded:
- Pods
- Derived
- DerivedData
💡
SwiftLint
can automatically display linting warnings and errors in Xcode during code compilation, helping developers fix issues during development. Setup instructions are available on the project’s GitHub page.
Now that we’ve covered SwiftLint
, let’s explore how Danger
can automate our linting workflow.
SwiftLint with Danger
We will create a new Gemfile
and Dangerfile
inside the iosApp
directory, separated from the KMM modules.
Add the danger
and CocoaPods
gems to the iosApp/Gemfile
.
# Gemfile
source "https://rubygems.org"
gem "CocoaPods"
gem "danger"
gem "danger-SwiftLint"
Add SwiftLint
plugin to iosApp/Dangerfile
. Danger will execute SwiftLint from the plugin.
# Dangerfile
# SwiftLint
SwiftLint.binary_path = 'iosApp/Pods/SwiftLint/SwiftLint'
SwiftLint.config_file = 'iosApp/.SwiftLint.yml'
SwiftLint.max_num_violations = 20
SwiftLint.lint_files(
inline_mode: true,
fail_on_error: true, # Stop CI when fail
additional_SwiftLint_args: '--strict'
)
Create a CI/CD workflow that performs actions within the iosApp
subdirectory.
name: iOS Review pull request
on:
pull_request
defaults:
run:
working-directory: iosApp
jobs:
review_pull_request:
name: Review pull request
runs-on: macos-latest
timeout-minutes: 30
steps:
- name: Set up JAVA 11
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '11'
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
working-directory: 'iosApp'
- name: Install gems
run: |
bundle install
Danger will now run SwiftLint and comment on the files in a pull request.
Finally, add Danger execution to the end of CI/CD workflow such as this GitHub Actions example.
...
- name: Run iOS Danger
uses: MeilCli/danger-action@v5
with:
plugins_file: 'iosApp/Gemfile'
install_path: 'iosApp/vendor/bundle'
danger_file: 'iosApp/Dangerfile'
danger_id: 'danger-pr'
env:
DANGER_GITHUB_API_TOKEN: $

Linting with SwiftFormat
SwiftFormat is a code reformatter for Swift. It supports most of SwiftLint’s format and a few extra. SwiftFormat automatically formats your Swift code before pushing, ensuring it adheres to project conventions.
With Danger, SwiftFormat can be used for linting Swift code on the CI/CD, to check for some conventions that SwiftLint is missing.
Adding SwiftFormat
Add SwiftFormat
as a CocoaPods dependency to the Podfile
target 'iosApp' do
# Other pods
# ...
pod 'SwiftFormat/CLI'
# ...
end
💡 The SwiftFormat Github page shows multiple ways to install SwiftFormat. Using CocoaPods is the preferred way because Danger will require the path for SwiftFormat later on.
SwiftFormat can now be executed by running iosApp/Pods/SwiftFormat/CommandLineTool/swiftformat
.
SwiftFormat
can be configured with iosApp/.swiftformat
file. Here is an example of .swiftformat
file.
# file options
--exclude Pods, Generated, **/*.generated.swift
# rules
--disable fileHeader
--disable initCoderUnavailable
SwiftFormat with Danger
Add the SwiftFormat Danger plugin to the iosApp/Gemfile
.
# Gemfile
source "https://rubygems.org"
gem "CocoaPods"
gem "danger"
gem 'danger-swiftformat'
The last step is to add SwiftFormat
to iosApp/Dangerfile
.
# SwiftFormat
swiftformat.binary_path = 'iosApp/Pods/SwiftFormat/CommandLineTool/swiftformat'
swiftformat.exclude = %w(Pods/** **/*generated.swift)
swiftformat.check_format
Make sure to bundle exec danger
or use the Danger action in the CI/CD workflow.

Conclusion
Linting and formatting are essential for maintaining a clean, high-quality codebase in KMM projects. By integrating SwiftLint
, SwiftFormat
, and Danger
, you can automate these checks, streamline code reviews, and prevent technical debt from creeping into your iOS codebase.
To learn more about code coverage with Danger
, make sure to read the upcoming KMM iOS Code Coverage blog post, or read about KMM Android lint and KMM Android code coverage.