Consistent Swift style in Xcode with SwiftLint

Code style can be a controversial topic and lead to some passionate debates between developers. The use of a tool to enforce a set of rules on style can be very helpful to avoid contentious arguments and ensure the code is consistent throughout a project. SwiftLint can easily be incorporated into Xcode projects to flag code style violations as either warnings or errors at compile time.



Integrate SwiftLint with Xcode

SwiftLint is available from GitHub and can be installed in a number of ways such as downloading a SwiftLint.pkg or using a HomeBrew command.

1brew install swiftlint

Once SwiftLint has been installed, it can be integrated with an Xcode project by adding a run phase in the Build Phase of the primary app target. Select the + button and "New Run Script Phase" and add the following script. The export statement is needed on silicon Macs because Homebrew installs the binaries into the /opt/homebrew/bin folder by default.

1export PATH="$PATH:/opt/homebrew/bin"
2if which swiftlint > /dev/null; then
3  swiftlint
4else
5  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
6fi

Add run script phase to Integrate SwiftLint with Xcode
Add run script phase to Integrate SwiftLint with Xcode



SwiftLint rule violation

The good news is that there are no SwiftLint default rule violations on a new project in Xcode. Once you discover SwiftLint, it is a good idea to immediately add it to every project from the start. Add a blank space after a Text view on a new iOS app. Now a warning is generated when the code is compiled.

 1struct ContentView: View {
 2    var body: some View {
 3        VStack {
 4            Text("Hello, world!") 
 5
 6            Text("second line")
 7
 8            Spacer()
 9        }
10    }
11}

This code violates the trailing_whitespace rule, which is enabled by default.

1+------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+
2| identifier                               | opt-in | correctable | enabled in your config | kind        | analyzer | configuration |
3+------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+
4| trailing_whitespace                      | no     | yes         | yes                    | style       | no       | warning, i... |

SwiftLint warning on extra blank space at the end of a line
SwiftLint warning on extra blank space at the end of a line



SwiftLint Rules

There are over 200 rules included in SwiftLint, and the Swift community continues to contribute more rules regularly. One way to see the SwiftLint rules is to run swiftlint rules command in Terminal. This will show the rules as well as a number of attributes on the rules such as whether or not the rule is opt-in or correctable.

These are the default SwiftLint rules as of version 0.46.5.

  1swiftlint rules
  2+------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+
  3| identifier                               | opt-in | correctable | enabled in your config | kind        | analyzer | configuration |
  4+------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+
  5| anonymous_argument_in_multiline_closure  | yes    | no          | no                     | idiomatic   | no       | warning       |
  6| anyobject_protocol                       | yes    | yes         | no                     | lint        | no       | warning       |
  7| array_init                               | yes    | no          | no                     | lint        | no       | warning       |
  8| attributes                               | yes    | no          | no                     | style       | no       | warning, a... |
  9| balanced_xctest_lifecycle                | yes    | no          | no                     | lint        | no       | warning       |
 10| block_based_kvo                          | no     | no          | yes                    | idiomatic   | no       | warning       |
 11| capture_variable                         | yes    | no          | no                     | lint        | yes      | warning       |
 12| class_delegate_protocol                  | no     | no          | yes                    | lint        | no       | warning       |
 13| closing_brace                            | no     | yes         | yes                    | style       | no       | warning       |
 14| closure_body_length                      | yes    | no          | no                     | metrics     | no       | warning: 2... |
 15| closure_end_indentation                  | yes    | yes         | no                     | style       | no       | warning       |
 16| closure_parameter_position               | no     | no          | yes                    | style       | no       | warning       |
 17| closure_spacing                          | yes    | yes         | no                     | style       | no       | warning       |
 18| collection_alignment                     | yes    | no          | no                     | style       | no       | warning, a... |
 19| colon                                    | no     | yes         | yes                    | style       | no       | warning, f... |
 20| comma                                    | no     | yes         | yes                    | style       | no       | warning       |
 21| comment_spacing                          | no     | yes         | yes                    | lint        | no       | warning       |
 22| compiler_protocol_init                   | no     | no          | yes                    | lint        | no       | warning       |
 23| computed_accessors_order                 | no     | no          | yes                    | style       | no       | warning, o... |
 24| conditional_returns_on_newline           | yes    | no          | no                     | style       | no       | warning, i... |
 25| contains_over_filter_count               | yes    | no          | no                     | performance | no       | warning       |
 26| contains_over_filter_is_empty            | yes    | no          | no                     | performance | no       | warning       |
 27| contains_over_first_not_nil              | yes    | no          | no                     | performance | no       | warning       |
 28| contains_over_range_nil_comparison       | yes    | no          | no                     | performance | no       | warning       |
 29| control_statement                        | no     | yes         | yes                    | style       | no       | warning       |
 30| convenience_type                         | yes    | no          | no                     | idiomatic   | no       | warning       |
 31| custom_rules                             | no     | no          | no                     | style       | no       | user-defin... |
 32| cyclomatic_complexity                    | no     | no          | yes                    | metrics     | no       | warning: 1... |
 33| deployment_target                        | no     | no          | yes                    | lint        | no       | warning, i... |
 34| discarded_notification_center_observer   | yes    | no          | no                     | lint        | no       | warning       |
 35| discouraged_assert                       | yes    | no          | no                     | idiomatic   | no       | warning       |
 36| discouraged_direct_init                  | no     | no          | yes                    | lint        | no       | warning, t... |
 37| discouraged_none_name                    | yes    | no          | no                     | idiomatic   | no       | warning       |
 38| discouraged_object_literal               | yes    | no          | no                     | idiomatic   | no       | warning, i... |
 39| discouraged_optional_boolean             | yes    | no          | no                     | idiomatic   | no       | warning       |
 40| discouraged_optional_collection          | yes    | no          | no                     | idiomatic   | no       | warning       |
 41| duplicate_enum_cases                     | no     | no          | yes                    | lint        | no       | error         |
 42| duplicate_imports                        | no     | no          | yes                    | idiomatic   | no       | warning       |
 43| duplicated_key_in_dictionary_literal     | no     | no          | yes                    | lint        | no       | warning       |
 44| dynamic_inline                           | no     | no          | yes                    | lint        | no       | error         |
 45| empty_collection_literal                 | yes    | no          | no                     | performance | no       | warning       |
 46| empty_count                              | yes    | no          | no                     | performance | no       | error, onl... |
 47| empty_enum_arguments                     | no     | yes         | yes                    | style       | no       | warning       |
 48| empty_parameters                         | no     | yes         | yes                    | style       | no       | warning       |
 49| empty_parentheses_with_trailing_closure  | no     | yes         | yes                    | style       | no       | warning       |
 50| empty_string                             | yes    | no          | no                     | performance | no       | warning       |
 51| empty_xctest_method                      | yes    | no          | no                     | lint        | no       | warning       |
 52| enum_case_associated_values_count        | yes    | no          | no                     | metrics     | no       | warning: 5... |
 53| expiring_todo                            | yes    | no          | no                     | lint        | no       | (approachi... |
 54| explicit_acl                             | yes    | no          | no                     | idiomatic   | no       | warning       |
 55| explicit_enum_raw_value                  | yes    | no          | no                     | idiomatic   | no       | warning       |
 56| explicit_init                            | yes    | yes         | no                     | idiomatic   | no       | warning       |
 57| explicit_self                            | yes    | yes         | no                     | style       | yes      | warning       |
 58| explicit_top_level_acl                   | yes    | no          | no                     | idiomatic   | no       | warning       |
 59| explicit_type_interface                  | yes    | no          | no                     | idiomatic   | no       | warning, e... |
 60| extension_access_modifier                | yes    | no          | no                     | idiomatic   | no       | warning       |
 61| fallthrough                              | yes    | no          | no                     | idiomatic   | no       | warning       |
 62| fatal_error_message                      | yes    | no          | no                     | idiomatic   | no       | warning       |
 63| file_header                              | yes    | no          | no                     | style       | no       | warning, r... |
 64| file_length                              | no     | no          | yes                    | metrics     | no       | warning: 4... |
 65| file_name                                | yes    | no          | no                     | idiomatic   | no       | (severity)... |
 66| file_name_no_space                       | yes    | no          | no                     | idiomatic   | no       | (severity)... |
 67| file_types_order                         | yes    | no          | no                     | style       | no       | warning, o... |
 68| first_where                              | yes    | no          | no                     | performance | no       | warning       |
 69| flatmap_over_map_reduce                  | yes    | no          | no                     | performance | no       | warning       |
 70| for_where                                | no     | no          | yes                    | idiomatic   | no       | warning       |
 71| force_cast                               | no     | no          | yes                    | idiomatic   | no       | error         |
 72| force_try                                | no     | no          | yes                    | idiomatic   | no       | error         |
 73| force_unwrapping                         | yes    | no          | no                     | idiomatic   | no       | warning       |
 74| function_body_length                     | no     | no          | yes                    | metrics     | no       | warning: 4... |
 75| function_default_parameter_at_end        | yes    | no          | no                     | idiomatic   | no       | warning       |
 76| function_parameter_count                 | no     | no          | yes                    | metrics     | no       | warning: 5... |
 77| generic_type_name                        | no     | no          | yes                    | idiomatic   | no       | (min_lengt... |
 78| ibinspectable_in_extension               | yes    | no          | no                     | lint        | no       | warning       |
 79| identical_operands                       | yes    | no          | no                     | lint        | no       | warning       |
 80| identifier_name                          | no     | no          | yes                    | style       | no       | (min_lengt... |
 81| implicit_getter                          | no     | no          | yes                    | style       | no       | warning       |
 82| implicit_return                          | yes    | yes         | no                     | style       | no       | warning, i... |
 83| implicitly_unwrapped_optional            | yes    | no          | no                     | idiomatic   | no       | warning, m... |
 84| inclusive_language                       | no     | no          | yes                    | style       | no       | warning, a... |
 85| indentation_width                        | yes    | no          | no                     | style       | no       | severity: ... |
 86| inert_defer                              | no     | no          | yes                    | lint        | no       | warning       |
 87| is_disjoint                              | no     | no          | yes                    | idiomatic   | no       | warning       |
 88| joined_default_parameter                 | yes    | yes         | no                     | idiomatic   | no       | warning       |
 89| large_tuple                              | no     | no          | yes                    | metrics     | no       | warning: 2... |
 90| last_where                               | yes    | no          | no                     | performance | no       | warning       |
 91| leading_whitespace                       | no     | yes         | yes                    | style       | no       | warning       |
 92| legacy_cggeometry_functions              | no     | yes         | yes                    | idiomatic   | no       | warning       |
 93| legacy_constant                          | no     | yes         | yes                    | idiomatic   | no       | warning       |
 94| legacy_constructor                       | no     | yes         | yes                    | idiomatic   | no       | warning       |
 95| legacy_hashing                           | no     | no          | yes                    | idiomatic   | no       | warning       |
 96| legacy_multiple                          | yes    | no          | no                     | idiomatic   | no       | warning       |
 97| legacy_nsgeometry_functions              | no     | yes         | yes                    | idiomatic   | no       | warning       |
 98| legacy_objc_type                         | yes    | no          | no                     | idiomatic   | no       | warning       |
 99| legacy_random                            | yes    | no          | no                     | idiomatic   | no       | warning       |
100| let_var_whitespace                       | yes    | no          | no                     | style       | no       | warning       |
101| line_length                              | no     | no          | yes                    | metrics     | no       | warning: 1... |
102| literal_expression_end_indentation       | yes    | yes         | no                     | style       | no       | warning       |
103| lower_acl_than_parent                    | yes    | no          | no                     | lint        | no       | warning       |
104| mark                                     | no     | yes         | yes                    | lint        | no       | warning       |
105| missing_docs                             | yes    | no          | no                     | lint        | no       | warning: o... |
106| modifier_order                           | yes    | yes         | no                     | style       | no       | warning, p... |
107| multiline_arguments                      | yes    | no          | no                     | style       | no       | warning, f... |
108| multiline_arguments_brackets             | yes    | no          | no                     | style       | no       | warning       |
109| multiline_function_chains                | yes    | no          | no                     | style       | no       | warning       |
110| multiline_literal_brackets               | yes    | no          | no                     | style       | no       | warning       |
111| multiline_parameters                     | yes    | no          | no                     | style       | no       | warning, a... |
112| multiline_parameters_brackets            | yes    | no          | no                     | style       | no       | warning       |
113| multiple_closures_with_trailing_closure  | no     | no          | yes                    | style       | no       | warning       |
114| nesting                                  | no     | no          | yes                    | metrics     | no       | (type_leve... |
115| nimble_operator                          | yes    | yes         | no                     | idiomatic   | no       | warning       |
116| no_extension_access_modifier             | yes    | no          | no                     | idiomatic   | no       | error         |
117| no_fallthrough_only                      | no     | no          | yes                    | idiomatic   | no       | warning       |
118| no_grouping_extension                    | yes    | no          | no                     | idiomatic   | no       | warning       |
119| no_space_in_method_call                  | no     | yes         | yes                    | style       | no       | warning       |
120| notification_center_detachment           | no     | no          | yes                    | lint        | no       | warning       |
121| nslocalizedstring_key                    | yes    | no          | no                     | lint        | no       | warning       |
122| nslocalizedstring_require_bundle         | yes    | no          | no                     | lint        | no       | warning       |
123| nsobject_prefer_isequal                  | no     | no          | yes                    | lint        | no       | warning       |
124| number_separator                         | yes    | yes         | no                     | style       | no       | warning, m... |
125| object_literal                           | yes    | no          | no                     | idiomatic   | no       | warning, i... |
126| opening_brace                            | no     | yes         | yes                    | style       | no       | warning, a... |
127| operator_usage_whitespace                | yes    | yes         | no                     | style       | no       | warning, l... |
128| operator_whitespace                      | no     | no          | yes                    | style       | no       | warning       |
129| optional_enum_case_matching              | yes    | yes         | no                     | style       | no       | warning       |
130| orphaned_doc_comment                     | no     | no          | yes                    | lint        | no       | warning       |
131| overridden_super_call                    | yes    | no          | no                     | lint        | no       | warning, e... |
132| override_in_extension                    | yes    | no          | no                     | lint        | no       | warning       |
133| pattern_matching_keywords                | yes    | no          | no                     | idiomatic   | no       | warning       |
134| prefer_nimble                            | yes    | no          | no                     | idiomatic   | no       | warning       |
135| prefer_self_in_static_references         | yes    | yes         | no                     | style       | no       | N/A           |
136| prefer_self_type_over_type_of_self       | yes    | yes         | no                     | style       | no       | warning       |
137| prefer_zero_over_explicit_init           | yes    | yes         | no                     | idiomatic   | no       | warning       |
138| prefixed_toplevel_constant               | yes    | no          | no                     | style       | no       | warning, o... |
139| private_action                           | yes    | no          | no                     | lint        | no       | warning       |
140| private_outlet                           | yes    | no          | no                     | lint        | no       | warning, a... |
141| private_over_fileprivate                 | no     | yes         | yes                    | idiomatic   | no       | warning, v... |
142| private_subject                          | yes    | no          | no                     | lint        | no       | warning       |
143| private_unit_test                        | no     | no          | yes                    | lint        | no       | warning: X... |
144| prohibited_interface_builder             | yes    | no          | no                     | lint        | no       | warning       |
145| prohibited_super_call                    | yes    | no          | no                     | lint        | no       | warning, e... |
146| protocol_property_accessors_order        | no     | yes         | yes                    | style       | no       | warning       |
147| quick_discouraged_call                   | yes    | no          | no                     | lint        | no       | warning       |
148| quick_discouraged_focused_test           | yes    | no          | no                     | lint        | no       | warning       |
149| quick_discouraged_pending_test           | yes    | no          | no                     | lint        | no       | warning       |
150| raw_value_for_camel_cased_codable_enum   | yes    | no          | no                     | lint        | no       | warning       |
151| reduce_boolean                           | no     | no          | yes                    | performance | no       | warning       |
152| reduce_into                              | yes    | no          | no                     | performance | no       | warning       |
153| redundant_discardable_let                | no     | yes         | yes                    | style       | no       | warning       |
154| redundant_nil_coalescing                 | yes    | yes         | no                     | idiomatic   | no       | warning       |
155| redundant_objc_attribute                 | no     | yes         | yes                    | idiomatic   | no       | warning       |
156| redundant_optional_initialization        | no     | yes         | yes                    | idiomatic   | no       | warning       |
157| redundant_set_access_control             | no     | no          | yes                    | idiomatic   | no       | warning       |
158| redundant_string_enum_value              | no     | no          | yes                    | idiomatic   | no       | warning       |
159| redundant_type_annotation                | yes    | yes         | no                     | idiomatic   | no       | warning       |
160| redundant_void_return                    | no     | yes         | yes                    | idiomatic   | no       | warning       |
161| required_deinit                          | yes    | no          | no                     | lint        | no       | warning       |
162| required_enum_case                       | yes    | no          | no                     | lint        | no       | No protoco... |
163| return_arrow_whitespace                  | no     | yes         | yes                    | style       | no       | warning       |
164| self_in_property_initialization          | no     | no          | yes                    | lint        | no       | warning       |
165| shorthand_operator                       | no     | no          | yes                    | style       | no       | error         |
166| single_test_class                        | yes    | no          | no                     | style       | no       | warning       |
167| sorted_first_last                        | yes    | no          | no                     | performance | no       | warning       |
168| sorted_imports                           | yes    | yes         | no                     | style       | no       | warning       |
169| statement_position                       | no     | yes         | yes                    | style       | no       | (statement... |
170| static_operator                          | yes    | no          | no                     | idiomatic   | no       | warning       |
171| strict_fileprivate                       | yes    | no          | no                     | idiomatic   | no       | warning       |
172| strong_iboutlet                          | yes    | yes         | no                     | lint        | no       | warning       |
173| superfluous_disable_command              | no     | no          | yes                    | lint        | no       | warning       |
174| switch_case_alignment                    | no     | no          | yes                    | style       | no       | warning, i... |
175| switch_case_on_newline                   | yes    | no          | no                     | style       | no       | warning       |
176| syntactic_sugar                          | no     | yes         | yes                    | idiomatic   | no       | warning       |
177| test_case_accessibility                  | yes    | yes         | no                     | lint        | no       | warning, a... |
178| todo                                     | no     | no          | yes                    | lint        | no       | warning       |
179| toggle_bool                              | yes    | yes         | no                     | idiomatic   | no       | warning       |
180| trailing_closure                         | yes    | no          | no                     | style       | no       | warning, o... |
181| trailing_comma                           | no     | yes         | yes                    | style       | no       | warning, m... |
182| trailing_newline                         | no     | yes         | yes                    | style       | no       | warning       |
183| trailing_semicolon                       | no     | yes         | yes                    | idiomatic   | no       | warning       |
184| trailing_whitespace                      | no     | yes         | yes                    | style       | no       | warning, i... |
185| type_body_length                         | no     | no          | yes                    | metrics     | no       | warning: 2... |
186| type_contents_order                      | yes    | no          | no                     | style       | no       | warning, o... |
187| type_name                                | no     | no          | yes                    | idiomatic   | no       | (min_lengt... |
188| unavailable_function                     | yes    | no          | no                     | idiomatic   | no       | warning       |
189| unneeded_break_in_switch                 | no     | no          | yes                    | idiomatic   | no       | warning       |
190| unneeded_parentheses_in_closure_argument | yes    | yes         | no                     | style       | no       | warning       |
191| unowned_variable_capture                 | yes    | no          | no                     | lint        | no       | warning       |
192| untyped_error_in_catch                   | yes    | yes         | no                     | idiomatic   | no       | warning       |
193| unused_capture_list                      | no     | no          | yes                    | lint        | no       | warning       |
194| unused_closure_parameter                 | no     | yes         | yes                    | lint        | no       | warning       |
195| unused_control_flow_label                | no     | yes         | yes                    | lint        | no       | warning       |
196| unused_declaration                       | yes    | no          | no                     | lint        | yes      | severity: ... |
197| unused_enumerated                        | no     | no          | yes                    | idiomatic   | no       | warning       |
198| unused_import                            | yes    | yes         | no                     | lint        | yes      | severity: ... |
199| unused_optional_binding                  | no     | no          | yes                    | style       | no       | warning, i... |
200| unused_setter_value                      | no     | no          | yes                    | lint        | no       | warning       |
201| valid_ibinspectable                      | no     | no          | yes                    | lint        | no       | warning       |
202| vertical_parameter_alignment             | no     | no          | yes                    | style       | no       | warning       |
203| vertical_parameter_alignment_on_call     | yes    | no          | no                     | style       | no       | warning       |
204| vertical_whitespace                      | no     | yes         | yes                    | style       | no       | warning, m... |
205| vertical_whitespace_between_cases        | yes    | yes         | no                     | style       | no       | warning       |
206| vertical_whitespace_closing_braces       | yes    | yes         | no                     | style       | no       | N/A           |
207| vertical_whitespace_opening_braces       | yes    | yes         | no                     | style       | no       | N/A           |
208| void_return                              | no     | yes         | yes                    | style       | no       | warning       |
209| weak_delegate                            | yes    | yes         | no                     | lint        | no       | warning       |
210| xct_specific_matcher                     | yes    | no          | no                     | idiomatic   | no       | warning       |
211| xctfail_message                          | no     | no          | yes                    | idiomatic   | no       | warning       |
212| yoda_condition                           | yes    | no          | no                     | lint        | no       | warning       |
213+------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+


Run SwiftLint in Terminal

SwiftLint can be configured to run as pre-commit hook on a repository to ensure code checked-in is consistent. It can also be run as a command in a Terminal simply by running swiftlint in the project directory. run swiftlint --help for more options.

 1swiftlint --help
 2OVERVIEW: A tool to enforce Swift style and conventions.
 3
 4USAGE: swiftlint <subcommand>
 5
 6OPTIONS:
 7  --version               Show the version.
 8  -h, --help              Show help information.
 9
10SUBCOMMANDS:
11  analyze                 Run analysis rules
12  docs                    Open SwiftLint documentation website in the default web browser
13  generate-docs           Generates markdown documentation for all rules
14  lint (default)          Print lint warnings and errors
15  rules                   Display the list of rules and their identifiers
16  version                 Display the current version of SwiftLint
17
18  See 'swiftlint help <subcommand>' for detailed help.

Running swiftlint in a Terminal shows the same violation of the trailing_whitespace rule in the HelloSwiftLintApp.

1swiftlint
2Linting Swift files in current working directory
3Linting 'HelloSwiftLintApp.swift' (1/2)
4Linting 'ContentView.swift' (2/2)
5...HelloSwiftLint/ContentView.swift:13:1: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)
6Done linting! Found 1 violation, 0 serious in 2 files.


Automatically fix SwiftLint violations

It can be seen from the list of rules above that some of these can be automatically corrected. This is straight-forward for spacing rules - like the extra white space introduced above. Automatically fixing SwiftLint rule violations can be done wherever SwiftLint analysis can be done.

Running the swiftlint --fix in a Terminal will fix all the SwiftLint violations that can be fixed automatically.

1swiftlint --fix
2Correcting Swift files in current working directory
3Correcting 'HelloSwiftLintApp.swift' (1/2)
4Correcting 'ContentView.swift' (2/2)
5.../HelloSwiftLint/ContentView.swift:13:1 Corrected Trailing Whitespace
6Done inspecting 2 files for auto-correction!

Alternatively, the automatic fix can be incorporated into the Build Phase of the project in Xcode. Edit the "Run Script Phase" of the project to include fixing the SwiftLint violations. Now, adding a trailing space is automatically removed when the code is compiled in Xcode.

1export PATH="$PATH:/opt/homebrew/bin"
2if which swiftlint > /dev/null; then
3  swiftlint --fix && swiftlint
4else
5  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
6fi

Integrate SwiftLint to automatically fix rule violations at compile time where possible
Integrate SwiftLint to automatically fix rule violations at compile time where possible



Manually fix SwiftLint rule violations

Not all rule violations can be fixed automatically. There are a number of options available to deal with warnings and errors generated by SwiftLint analysis. If there are only one or two issues, then the best approach is to fix them and move on.

Options to deal with SwiftLint rule violations:

1. Modify the code to be compliant with the SwiftLint rules
2. Add exception in code to ignore specific rule violations
3. Customise the SwiftLint rules for the project
4. Ignore the warnings - not a good option

Fix the issues is the best approach and this can be easily achieved when SwiftLint is incorporated into the project from the start.

Here is a sample of code I've written that loops through an array of vertices and creates a path. Using enumerated method which yields the index number and the item in the array. Using a single character n for a variable causes a compile time error and the use of only characters causes a warning.

1    for (n, pt) in vertices.enumerated() {
2        n == 0 ? path.move(to: pt) : path.addLine(to: pt)
3    }

These "identifier-name" violations cannot be fixed automatically. It can be tempting to create exceptions for these, but it will help, in the long run, to be more descriptive of the identifiers and make the code easier to maintain.

1    for (index, point) in vertices.enumerated() {
2        index == 0 ? path.move(to: point) : path.addLine(to: point)
3    }

I notice that the sample code from Apple on enumerated has similar issues!

SwiftLint identifier-name violations that cannot be fixed automatically
SwiftLint identifier-name violations that cannot be fixed automatically



Exceptions to the rule

There are circumstances where the code needs to be compatible with some external API or data source. The Open Weather API provides data in JSON format as described in Read JSON with codeable in Swift. The weather data contains data in the main section with identifier names containing underscores, such as feels_like. The Swift struct used to decode this JSON has to match the names in JSON, so the Swift code yields compile time errors because of SwiftLint rule "identifier_name" violation.

Sample JSON weather data

 1{
 2    "coord": {
 3        "lon": -122.08,
 4        "lat": 37.39
 5    },
 6    "weather": [
 7        {
 8            "id": 800,
 9            "main": "Clear",
10            "description": "clear sky",
11            "icon": "01d"
12        }
13    ],
14    "base": "stations",
15    "main": {
16        "temp": 9.4,
17        "feels_like": 8.71,
18        "temp_min": 7.22,
19        "temp_max": 11.11,
20        "pressure": 1023,
21        "humidity": 100,
22        "sea_level": 100
23    },
24    "visibility": 16093,
25    "wind": {
26        "speed": 1.5,
27        "deg": 350
28    },
29    "clouds": {
30        "all": 1
31    },
32    "dt": 1560350645,
33    "sys": {
34        "type": 1,
35        "id": 5122,
36        "message": 0.0139,
37        "country": "US",
38        "sunrise": 1560343627,
39        "sunset": 1560396563
40    },
41    "timezone": -25200,
42    "id": 420006353,
43    "name": "Mountain View",
44    "cod": 200
45}
 1struct WeatherRawData: Codable {
 2    var name: String
 3    var timezone: Double
 4    var weather: [WeatherData]
 5    var main: MainData
 6    var wind: WindData
 7    var clouds: CloudsData
 8
 9    struct WeatherData: Codable {
10        var id: Double
11        var main: String
12        var description: String
13        var icon: String
14    }
15
16    struct MainData: Codable {
17        var temp: Double
18        var feels_like: Double
19        var temp_min: Double
20        var temp_max: Double
21        var pressure: Double
22        var humidity: Double
23    }
24
25    struct WindData: Codable {
26        var speed: Double
27        var deg: Double
28    }
29
30    struct CloudsData: Codable {
31        var all: Double
32    }
33}

SwiftLint identifier-name violations that cannot be cannot be changed
SwiftLint identifier-name violations that cannot be cannot be changed


It is not necessary to throw everything out. In this case, it is possible to simply disable the SwiftLint rule before the offending code and re-enable the rule afterwards. Obviously, it would not be great if these enable/disable snippets were sprinkled thoughtout the code. This technique should be used sparingly, being the exception that proves the rule. If it is found that the same rule needs to be disable in a number of places, consider disabling the rule for the entire project.

 1struct WeatherRawData: Codable {
 2    var name: String
 3    var timezone: Double
 4    var weather: [WeatherData]
 5    var main: MainData
 6    var wind: WindData
 7    var clouds: CloudsData
 8
 9    struct WeatherData: Codable {
10        var id: Double
11        var main: String
12        var description: String
13        var icon: String
14    }
15
16    // swiftlint:disable identifier_name
17    struct MainData: Codable {
18        var temp: Double
19        var feels_like: Double
20        var temp_min: Double
21        var temp_max: Double
22        var pressure: Double
23        var humidity: Double
24    }
25    // swiftlint:enable identifier_name
26
27    struct WindData: Codable {
28        var speed: Double
29        var deg: Double
30    }
31
32    struct CloudsData: Codable {
33        var all: Double
34    }
35}

Disable SwiftLint specific rules for a section of code
Disable SwiftLint specific rules for a section of code



Don't rush to disable rules

There will occasionally be exceptions to the SwiftLint rules, but don't be too quick to disable a rule. In the above example, there is a better way to map variable names in Swift code to the JSON content using CodingKey. Rather than commenting out the SwiftLint rules, use a swift property name feelsLike and specify the alternative value of feels_like to match the JSON data.

 1    // swiftlint:disable identifier_name
 2    struct MainData: Codable {
 3        var temp: Double
 4        var feels_like: Double
 5        var temp_min: Double
 6        var temp_max: Double
 7        var pressure: Double
 8        var humidity: Double
 9    }
10    // swiftlint:enable identifier_name
 1struct MainData: Codable {
 2    let temp: Double
 3    let feelsLike: Double
 4    let tempMin: Double
 5    let tempMax: Double
 6    let pressure: Double
 7    let humidity: Double
 8
 9    enum CodingKeys: String, CodingKey {
10        case temp
11        case feelsLike = "feels_like"
12        case tempMin = "temp_min"
13        case tempMax = "temp_max"
14        case pressure
15        case humidity
16    }
17}

Use of CodingKeys to map JSON variables rather than disabling SwiftLint rules
Use of CodingKeys to map JSON variables rather than disabling SwiftLint rules



Customise SwiftLint rules

The approach of "Fix all the rule violations" can be more difficult to do when SwiftLint is added to an existing project that show hundreds of issues. In this case it can be more appropriate to add a SwiftLint configuration to the project. This is a YAML file where it is possible to disable rules, list opt-in rules or limit rules to only rules in this file. In this way SwiftLint is endlessly customisable. More details are available on SwiftLint Configuration section.

An example of a warning is the presence of TODO comments in the codebase. SwiftLint will flag these as warnings as they are flags to places in the code that still have work remaining.

TODO comments in the code default to compiler warnings by SwiftLint
TODO comments in the code default to compiler warnings by SwiftLint

There may be times when you want to merge in the current code and leave the TODO comments in place, but it is also desired to have the code compile without warnings. Each individual TODO comment could have disable/enable placed before it or a ".swiftlint.yml" file could be added to disable this rule for the entire project. The following .swiftlint.yml file added to the project now allows the project to compile without a warning for the TODO comment. The other SwiftLint rules are unaffected.

1disabled_rules: # rule identifiers to exclude from running
2  - todo
 1struct ContentView: View {
 2
 3    var body: some View {
 4        VStack {
 5            Text("Hello, world!")
 6
 7            // TODO: Remove this function when done
 8
 9            let a = 24
10            Text("second line \(a)")
11
12            Spacer()
13        }
14    }
15}

TODO comments not causing warning but other SwiftLint rules applied
TODO comments not causing warning but other SwiftLint rules applied


The easiest way to get started with SwiftLint on an existing project is:

  1. Install SwiftLint
  2. Integrate SwiftLint into the Xcode project with a build phase script
  3. Compile to assess all the warnings and errors
  4. Add a ".swiftlint.yml" file with the rules with the highest count violations disabled
  5. Enable one rule at a time and fix the issues in the code



Conclusion

Use of SwiftLint is a must for any Swift development. It helps to avoid arguments over code style on a team and it helps create consistent code style in the codebase. In my case, it helps me from bad habits like creating single letter identifiers!

Adding SwiftLint to an existing codebase can be more work that adding to a new project as it can show hundreds of warnings and errors. SwiftLint can be adopted into and existing project by configuring the rules and turning on more rules over time.

The ability for SwiftLint to automatically fix rule violations is fantastic and the initial sight of hundreds of violations can be significantly addressed by fixing automatically - just ensure the code is checked into version control before making widespread automatic changes, so it is easy to reverse if something goes wrong.