Be sure to have the values below set in Xcode
> Settings
> TextEditing
Display | Editing | Indentation |
---|---|---|
Enable spell checking under Edit
> Format
> Spelling and Grammar
> Check Spelling While Typing
. We use en-UK
as default language.
This way you avoid limitless growing codebase plus, more important, you avoid fixing issues in multiple places. This is one of the most important rules.
Switch is an extremely useful construct in Swift since the compiler warns about missing cases. If you use the default case you disable this behaviour and loose one of the most powerful features of Swift. It makes sense however to use the default case if you don't want case-specific behavior for every case, but only for one or some of them. In this case it doesn't break anything if new cases get added. Also if you switch over plain value types, a default case is harmless.
Imagine you create a string-typed enum and omit the values for every case since Swift infers the value from the case name. Now when you refactor one enum case from somewhere in your app, the inferred value also changes, which is not desired i.e. in JSON keys. You would introduce hard to find errors.
Your own implementation could easily be confused with Optional.none
when your enum typed property is optional. To avoid this think about an alternative name for this case.
Because super- and subclass logic becomes hard to maintain, try to split up logic into separate structs or classes instead. By marking your classes final, some optimizations are enabled which among other things speeds up compilation. Remember that since structs are not inheritable, they are final by design.
When you set the thickness of lines to 1.0 / UIScreen.main.scale
then the will appear equally on all devices.
If don't folow this rule, F depends on the target/module where the string is defined and thus is not modular/independent anymore. If e.g. another target/module uses a class of F that contains a localized string which is outside of F the translation could not be found.
Having a unified pattern for naming selectors helps to make code understandable faster. The on* pattern is also used in other programming languages like JavaScript.
They are harder to read. They are harder to understand. They are even not visible when you work in multiple editor windows side by side - or even worse break to the next line. They could be misunderstood by new developers as an indicator to use it everywhere which will greatly degrade the codebase.
For example when you have to declare a property in a specific unit like timeInSeconds
you usually call it time
and use the type TimeInterval
which is nothing more than a typalias. Use them to create an implicit context and keep property names swiftily short. Remember: They also appear in code completion and give you a hint about the property that you cannot infer from a short name like in getWeight() -> Kilograms
. The latter example is a much better declaration than getWeight() -> Double
. Read more about that in Sundells Blog Article.
Use Camel Case for acronyms like URL or HTML
For names like isHtmlValid
or isUrlComplete
this improves readability of function and variable names (though it is different than Apple does it). Also see this aticle.
Use semantic colors instead of red
, blue
, etc. Semantic colors describe in which context they are used instead of which color they actually are. Good examples are backgroundColor
, buttonColor
, buttonHighlightColor
, labelColor
, etc. Using semantic colors enables the color to automatically adapt to changing OS settiongs like dark mode or high contrast mode.
We have the functions <subview>.addTo(<parent>)
and <subview>.addMaximizedTo(<parent>)
which do that automatically.
Use UIAlertController extension to display alerts and action sheets.
Never define a font in code, but rather use or extend the presets in the Fonts enum.
Never define a color in code, but rather use or extend the Palette struct and the related UIColor extension.
You don'thave to configure your own DateFormatter
objects, use SHDateFormatter
instead. If it doesn't contain a date format you need, extend it in SHDateFormatter+Extensions
.
Use the showArrangedSubview
method in the UIView+Extensions
to show or hide arranged subviews.
Before implementing extensions on any build-in types, have a look in the Extensions
framework. We already implemented a lot and most likely it is already there. The rule is when it has no app dpeendencies it goes to the Extensions
framework otherwise it should be part of the app.
These guidelines are intended for code-based view/view controller creation without Interface Builder and teach you how to work Auto Layout programmatically.
Setup each subview of a view or view controller in its own function! The Name of the function has to be private func setup<property_name>
. This function should set all parameters necessary for the first setup. This includes contentHuggingPriority
and contentCompressionResistencyPriority
. It is called only once in init (UIView) or viewDidLoad (UIViewController). Right before the function returns add the view to its superview. This pattern turned out to be one of the cleanest structuring methods possible for the swift language. The advantage is that you have a clean interface of properties on top of the class which helps to recognize all subviews at one glance - in contrast of setting up views in a property closure. Furthermore you can use self
since these functions are called after initialization. You have a clear overview of the adding order of subviews. Additionally you prevent one long function that sets up all properties of all subviews - typically init or viewDidLoad. Last but not least if you stick to this pattern it is much easier to understand the structure of the whole app since you'll find this pattern all over again.
Always use private func setupAutoLayout()
to create constraints between all subviews! Call it right after the last private func setup*
function in viewDidLoad (UIViewController) or init (UIView). This activates all layout constraints.
Never access the view in the initializer of a UIViewController subclass. Accessing the view property causes forces it to be loaded. You should never force the view to be loaded in the initializer since this is done automatically when the view is added to the view hierarchy. By default the view should be created in loadView()
. Additional configurations are done in viewDidLoad()
. After the view has been loaded you can start making modifications to it like adding subviews, etc.
Sometimes we initialize VCs just to pass it around. Setting up the whole view hierachy just blocks resources which we do not need at that moment.
To be able to use symbols in tests declare them as internal
(no prefix) instead of private
even if they are not used outside of the containing object. If you don't need to test them and they are not required outside of the containing object declare it as private
.
So it will become easier to track down future retain cycles that are caused by missing [weak self]
in the closure definition. Just leave out self
if it is not necessary. Also use the self.
syntax to initially assign constructor parameters to their respective properties if they have the same name.
Do not leave a blank line underneath a every function declaration. This results in more compact code blocks which can be perceived as one block easier.
Comment functions and properties using ///
only if it enhances their comprehensibility. Generally comment code snippets if you think it is reasonable. Sometimes design choices or complex structures are not immediately obvious to understand, in such cases comments are useful for others and your future self.
Remove comment header for newly created files since it will be always outdated (copyright, spelling errors in file names, etc.).
Place the nested types at the bottom of the class/struct definition with a MARK: - Sub-Types
above. Place protocol conformances above these nested types and mark them with their respective protocol name, e.g. MARK: - StatePresentable
.
All other symbols (properties, function, etc.) should be ordered by ascending privateness, so public
, internal
, private
.
static
symbols are always above non-static
ones.
Do not use extensions for organizing/modularizing your code! Instead use // MARK: - <#title#>
statements to define section in your code.
The advantages of this and specifically moving all protocols in the main object definition are:
- the main class declaration shows all conformances of the class/struct
- clean method list (
⌃+6
) - a unified file structure results in less searching for symbols, very similar to the
func setup<view>()
convention
See also the customizable SwiftLint rule type_contents_order.
This is easy to read and matches the swifty naming conventions in modern iOS frameworks.
Try to achieve a maximum line length of 80-120 characters. This has several reasons:
- Some developers work with 2 parallel editors (2 editors, 1 editor / 1 canvas, 1 editor / 1 assistant / etc.) and maybe also the simulator side by side.
- Horizontal scrolling is quite annoying
- Not every developer has a 4K monitor. Be inclusive!
- During presentations the font size has to be increased which reduces the available space in the editor
- Many command line applictaions work with 120 or 80 columns
In all of the cases above the editor's code should be fully readable without horizontal scrolling and there should be no need to constantly adjust the Xcode panels as this is pretty stressful.
Code that looks ugly with a line-length limit of 80-120 is a great indicator that refactoring is needed.
Please check out the SwiftLint rule line_length.
Typealiases have a bunch of advantages but for nested structs, this are the cons:
- When
⌘
-clicking the typealias, Xcode will take you to the alias. What you want instead is navigate to the original object. - Quick-Open (
⌘ + ⇧ + o
) leads you to the typealias. You actually want the original object as suggestion. It also can happen that you end up with multiple suggestions of the same type in the quick-open dialog (one for the alias and one for the real object). - When you add the same nested object under another parent struct, specifying the same aliases would be confusing. It could even lead to name-space conflicts, depending on where you declare them. In your codebase it hides the information which struct is actually meant.
- When you add a typealias for each nested struct, you would end up with tons of additional code on top of many files.
For these reasons I advise against using typealiases for nested objetcs. If you use them because of line length issues, meanwhile there are nice formatting options in Xcode that streamline indentation (⌃ + m
in combination with SwiftFormat works perfectly).
Scripts do not like whitespaces. Since we have a lot of automation by scripts, please use _
as replacement for them.
This article by Apple is a good introduction and reference for how to use Voice Over.
Use the accessibilityId
of a view to make it identifiable in Unit/UI Tests or Voice Over announcements
The syntax for those identifiers should be aid.<view_controller>.<ui_element>
or if further scoping is needed aid.<view_controller>.<parent_view>.<ui_element>
.
While buttons usually have it implicitly set, labels functioning as e.g. headers need need their traits set to .header
. The same applies for search fields. In cases were a UIView serves as a button, it's trait should be set to .button
.
Use it always when a new screen/popup/alert is displayed and you want to draw the users attention to it. Use it sparsely in other situations since it interrupts messages spoken by Voice Over on user interaction. An alternative would be to manually refocus an element and re-read it's label or hint, containing the new information.
Blind people cannot see the image. If you enable Voice over here it should be a meaningful and short description of what the image shows.
Focussable elements should be kept to the necessary minimum. For example, labels and controls that belong together should be grouped to one accessibility element. The element should have its label's text for its a11y label and the control as its action. Compare the title + switch cells in the iOS Settings App.
Use at least 4.5:1 for fonts <18pt and 3:1 for fonts >=18pt. You can also read in the official WCAG20 docs about this topic.
These views should adapt to different font sizes so the UI looks good when the user scales up/down the font in the iOS settings. Support of the highest Dynamic Type setting is enough. Larger Accessibility Sizes
are not equired for now.
Extract testing code to helper fuctions if you need to write the same testing code over and over again to test similar things. The problem here is that Xcode will report the failure in your helper function. This is easily fixable by writing your function like this: func verify(file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(1, 3, file: file, line: line) }
! The error message will jump to the call site of the extracted function.
XCTUnwrap requires only 1 line instead of at least 3 for the guard. It makes tests much more readable. The error message is even better and most important standardized: XCTUnwrap failed: expected non-nil value of type "UIDatePicker"
. No worries the it
closures of Quick will handle thrown errors correctly.
This is super important since only the author truely knows how to integrate the changes of a branch into the development branch. The author is responsible for the PR and after making the changes ready for review there should not be changes by anybody else becasue they can lead to issues which fall back to the author in the end. Instead ask the author to do the changes. If conflict resolution is especially difficult, work together with the developer who created the commit that triggers the conflict.
This basically happens during the review.