├── README.md └── rules ├── avoid-frame-bounds-properties.md ├── avoid-struct-closure-self.md ├── immutable-controller.md ├── property-hooks-for-outlets.md ├── separate-view-logic.md ├── table-view-cell-identifier.md ├── use-extensions-when-conforming-to-protocols.md └── use-lazy-actions.md /README.md: -------------------------------------------------------------------------------- 1 | # Wolox iOS style guide 2 | 3 | We follow [GitHub's Swift style guide](https://github.com/github/swift-style-guide) and we enforce it using [SwiftLint](https://github.com/realm/SwiftLint) and [Linterbot](https://github.com/guidomb/linterbot). We also follow [Apple's API design guidelines](https://swift.org/documentation/api-design-guidelines/). 4 | 5 | * [Extract view logic from controller](./rules/separate-view-logic.md) 6 | * [Immutable controllers](./rules/immutable-controller.md) 7 | * [Use lazy actions](./rules/use-lazy-actions.md) 8 | * Extract presentation logic into view models 9 | * [Use extensions when conforming to protocols](./rules/use-extensions-when-conforming-to-protocols.md) 10 | * Use private extensions for private methods 11 | * [Use property hooks to configure outlets](./rules/property-hooks-for-outlets.md) 12 | * [Avoid structs that use closure that capture self](./rules/avoid-struct-closure-self.md) 13 | * Dependencies should always be protocols. 14 | * [Table view cell identifiers should be inferred from the class name.](./rules/table-view-cell-identifier.md) 15 | * Public properties should appear before private ones. 16 | * [Avoid using frame and bounds properties to add child views.](./rules/avoid-frame-bounds-properties.md) 17 | -------------------------------------------------------------------------------- /rules/avoid-frame-bounds-properties.md: -------------------------------------------------------------------------------- 1 | ## Avoid using frame and bounds properties to add child views 2 | 3 | When adding child view controller's views to the view hierarchy programmatically avoid using the container's bound or frame properties to 4 | configure the contained view's frame. 5 | 6 | ## Rationale 7 | 8 | When we define complex views, we generally use Interface Builder to layout all the view components and some times, for reusability or to separate responsibilities, we extract some sections of the view into their own view controllers. The proper way to this is to define a container view, where the child view controller's view will be added, and use interface builder to set its layout constrains. Then once our controller view has been loaded we can instantiate all the child view controllers and add their view to the view hierarchy by adding them as a sub view of their corresponding container view. 9 | 10 | The idea is to leverage the power of auto-layouts to calculate the container view's size and position and then make the contained view fill the container view. A naive approach to implement this would be to use the container view's bounds to configure the contained view's frame. The problem with this approach is that, most probably, you will be adding the child component in the `loadView` or `viewDidLoad` method and at those moments the layout engine hasn't been run yet. Making the bounds property report wrong values. 11 | 12 | A common trick to overcome this problem is to setup the child view in the `viewDidLayoutSubviews` method and use a flag to mark if the child view has been configured. Even though this works it forces to maintain more state in the view controller and makes the layout engine do another pass after the child view has added. 13 | 14 | The proper way to do this would be use auto layout constrains to make the child view fill the content of the container view by pinning their edges. 15 | 16 | ## Example 17 | 18 | The following example shows how to add a child view controller's view in the view hierarchy. In this case we have a parent controller (for example the one that is presented as the window's root view controller ). This controller could be as complex as you can imagine and it could have several UI components. For the sake of this example all those components have been omitted. 19 | 20 | The important thing is that there is a `mapContainerView` that will be used (as its name suggests) to contain a map view from a child view controller. This container view's layout has been configured using auto-layouts with Interface Builder. The idea is that the map view fills the container view. The child view controller is added programmatically. 21 | 22 | ### Wrong way 23 | 24 | The wrong way of doing this would be to use the `mapContainerView`'s bound as 25 | the map view's frame. 26 | 27 | ```swift 28 | public final class MyViewController: UIViewController { 29 | 30 | @IBOutlet weak var mapContainerView: UIView! 31 | 32 | private lazy var mapViewController: MapViewController = MapViewController() 33 | 34 | override public func viewDidLoad(animated: Bool) { 35 | super.viewDidLoad(animated) 36 | 37 | mapViewController.willMoveToParentViewController(self) 38 | addChildViewController(mapViewController) 39 | mapViewController.didMoveToParentViewController(self) 40 | 41 | mapViewController.view.frame = mapContainerView.bounds 42 | mapContainerView.addSubview(mapViewController.view) 43 | } 44 | 45 | } 46 | ``` 47 | 48 | The example above will result in the map view configured with the wrong size. The next example works but requires to keep state in the view controller and forces another pass of the layout engine. 49 | 50 | ```swift 51 | public final class MyViewController: UIViewController { 52 | 53 | @IBOutlet weak var mapContainerView: UIView! 54 | 55 | private lazy var mapViewController: MapViewController = MapViewController() 56 | private var mapViewConfigured = false 57 | 58 | override public func viewDidLayoutSubviews() { 59 | if (!mapViewConfigured) { 60 | mapViewController.willMoveToParentViewController(self) 61 | addChildViewController(mapViewController) 62 | mapViewController.didMoveToParentViewController(self) 63 | 64 | mapViewController.view.frame = mapContainerView.bounds 65 | mapContainerView.addSubview(mapViewController.view) 66 | 67 | mapViewConfigured = true 68 | } 69 | } 70 | 71 | } 72 | ``` 73 | 74 | ### Right way 75 | 76 | The proper solution would be to use layout constrains to pin the map's view edges to the container's. A couple more of code lines but the map view will have the correct dimensions and its content will be able to adjust properly. 77 | 78 | If you find yourself repeating this code you can extract the logic of pinning a view into a container view in an extension on `UIView`. 79 | 80 | ```swift 81 | public final class MyViewController: UIViewController { 82 | 83 | @IBOutlet weak var mapContainerView: UIView! 84 | 85 | private lazy var mapViewController: MapViewController = MapViewController() 86 | 87 | override public func viewDidLoad(animated: Bool) { 88 | super.viewDidLoad(animated) 89 | 90 | mapViewController.willMoveToParentViewController(self) 91 | addChildViewController(mapViewController) 92 | mapViewController.didMoveToParentViewController(self) 93 | 94 | mapContainerView.addSubview(mapViewController.view) 95 | mapContainerView.translatesAutoresizingMaskIntoConstraints = false 96 | mapViewController.view.translatesAutoresizingMaskIntoConstraints = false 97 | 98 | // Pins map view into the container view on all edges 99 | mapContainerView.topAnchor.constraintEqualToAnchor(mapViewController.view.topAnchor).active = true 100 | mapContainerView.bottomAnchor.constraintEqualToAnchor(mapViewController.view.bottomAnchor).active = true 101 | mapContainerView.leadingAnchor.constraintEqualToAnchor(mapViewController.view.leadingAnchor).active = true 102 | mapContainerView.trailingAnchor.constraintEqualToAnchor(mapViewController.view.trailingAnchor).active = true 103 | } 104 | 105 | } 106 | ``` 107 | -------------------------------------------------------------------------------- /rules/avoid-struct-closure-self.md: -------------------------------------------------------------------------------- 1 | ## Avoid structs that use closure that capture self 2 | 3 | ### Description 4 | 5 | It is not advisable to have value types (`struct`) containing reference types (`class`). Furthermore, `structs` that use a closure that captures `self` must not be used, as it will lead to memory issues. 6 | 7 | Quoting Andy Matushack on [classes-vs-structs](https://www.objc.io/issues/16-swift/swift-classes-vs-structs/): 8 | 9 | > Value types containing code that executes without being called by its owner are often unpredictable and should generally be avoided. For example: a struct initializer might call dispatch_after to schedule some work. But passing an instance of this struct to a function would duplicate the scheduled effect, inexplicitly, since a copy would be made. Value types should be inert. 10 | 11 | > Value types containing references are not necessarily isolated and should generally be avoided: they carry a dependency on all other owners of that referent. These value types are also not readily interchangeable, since that external reference might be connected to the rest of your system in some complex way. 12 | 13 | ### Rationale 14 | 15 | This is because `structs`, as being value types, have value semantics, meaning that they will be copied on assign. Therefore, when we have a `struct` and we try to capture it via `self` (for example for an async operation) we are capturing a copy to the `struct` instead. 16 | 17 | Take a look at this: 18 | 19 | ```swift 20 | public struct Foo { 21 | 22 | private var _number: Int = 0 23 | 24 | public init(signal: Signal) { 25 | signal.observeNext { 26 | print($0) 27 | self._number = 1 28 | } 29 | } 30 | } 31 | ``` 32 | While this code seems safe, it is creating a new copy of `Foo` with a new value of `_number` whenever the signal emits a new value. 33 | 34 | To solve this problem, we should change the `struct` to a `class`. 35 | 36 | ```swift 37 | public class Foo { 38 | 39 | private var _number: Int = 0 40 | 41 | public init(signal: Signal) { 42 | signal.observeNext { 43 | print($0) 44 | self._number = 1 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | ### Example 51 | 52 | ```swift 53 | public struct Foo { 54 | 55 | private let signal: Signal 56 | 57 | public let observer: Observer 58 | 59 | public var text: String = "" 60 | 61 | public var disposable: Disposable! 62 | 63 | public init() { 64 | (signal, observer) = Signal.pipe() 65 | signal.observeNext { 66 | self.text = "\($0)" 67 | } 68 | } 69 | 70 | } 71 | 72 | let foo = Foo() 73 | 74 | foo.observer.sendNext(4) 75 | print(foo.text) // prints("") 76 | ``` 77 | It prints "" because `foo.text` equals "", we changed the text of the copy created by `self` on the closure but not the `text` value of `foo`. 78 | 79 | ####Right way: 80 | 81 | ```swift 82 | public class Foo { 83 | 84 | private let signal: Signal 85 | 86 | public let observer: Observer 87 | 88 | public var text: String = "" 89 | 90 | public var disposable: Disposable! 91 | 92 | public init() { 93 | (signal, observer) = Signal.pipe() 94 | signal.observeNext { [unowned self] in 95 | self.text = "\($0)" 96 | } 97 | } 98 | 99 | } 100 | 101 | 102 | 103 | let foo = Foo() 104 | 105 | foo.observer.sendNext(4) 106 | print(foo.text) // prints("4") 107 | ``` 108 | 109 | It now prints 4, because it is a class and we changed the text that `foo` was pointing to. Note that we had to use `[unowned self]` inside the closure because `foo` was already retained by signal (because signal is a property of foo). 110 | 111 | ### Further information 112 | 113 | https://www.objc.io/issues/16-swift/swift-classes-vs-structs/ -------------------------------------------------------------------------------- /rules/immutable-controller.md: -------------------------------------------------------------------------------- 1 | ## Immutable controller 2 | 3 | Controllers should be instantiated programmatically using the initializer that requires a view model. Storyboards should be avoided. The controller's 4 | view model should be a private instance constant. 5 | 6 | ## Rationale 7 | 8 | By avoiding storyboards and [separating view logic from the view controller](./rules/separate-view-logic.md) we have full control on how view controllers are instantiated without loosing the possibility to design our UI using Interface Builder. This allows us to declare a custom initializer making dependencies explicit. 9 | 10 | If the view controller is well designed, meaning that it has a single responsibility, all business logic is extracted in services and the presentation logic is extracted in view models. Then the only dependency should be its view model. 11 | 12 | By making the controller immutable we avoid having complex logic to keep 13 | the internal state up-to-date. The view controller should only bind the properties of the view model with the view. Changes in the view model should be exposed using [Signal](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/Swift/Signal.swift), [SignalProducer](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/Swift/SignalProducer.swift) or any of the observable [properties](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/Swift/Property.swift) from the [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) library. 14 | 15 | The controller's responsibility gets reduced to coordinate the interaction of the view model with the view and handle events from the Cocoa framework. 16 | 17 | ## Example 18 | 19 | ```swift 20 | final class UserProfileController: UIViewController { 21 | 22 | lazy var userProfileView: UserProfileView = UserProfileView.loadFromNib() 23 | 24 | private let _viewModel: UserProfileViewModel 25 | 26 | init(viewModel: UserProfileViewModel) { 27 | _viewModel = viewModel 28 | super.init(nibName: nil, bundle: nil) 29 | } 30 | 31 | required public init?(coder aDecoder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | override func loadView { 36 | view = userProfileView 37 | } 38 | 39 | override func viewDidLoad { 40 | super.viewDidLoad() 41 | bindViewModel() 42 | } 43 | 44 | } 45 | 46 | private extension UserProfileController { 47 | 48 | var willDealloc: SignalProducer<(), NoError> { 49 | return rac_willDeallocSignal() 50 | .toSignalProducer() 51 | .flatMapError { _ in SignalProducer.empty } 52 | .map { _ in () } 53 | } 54 | 55 | func bindViewModel() { 56 | userProfileView.nameTextField.text = viewModel.name 57 | userProfileView.emailTextField.text = viewModel.email 58 | viewModel.fetchAvatar 59 | .takeUntil(willDealloc) 60 | .startWithNext { [unowned self] avatar in 61 | self.userProfileView.avatarView.image = avatar 62 | } 63 | } 64 | 65 | } 66 | ``` 67 | 68 | Where some of the types that are being used in the `UserProfileController` 69 | could look like: 70 | 71 | ```swift 72 | struct User { 73 | 74 | let name: String 75 | let email: String 76 | let avatarURL: NSURL 77 | 78 | } 79 | 80 | enum ImageFetcherError { 81 | 82 | case InvalidImageFormat(NSData) 83 | case FetchError(NSError) 84 | 85 | } 86 | 87 | typealias ImageFetcher = NSURL -> SignalProducer 88 | 89 | final class UserProfileViewModel { 90 | 91 | var name: String { return _user.name } 92 | var email: String { return _user.email } 93 | var fetchAvatar: SignalProducer { 94 | return _fetchImage(_user.avatarURL) 95 | } 96 | 97 | private let _user: User 98 | private let _fetchImage: ImageFetcher 99 | 100 | init(user: User, fetchImage: ImageFetcher) { 101 | _user = user 102 | _fetchImage = fetchImage 103 | } 104 | 105 | } 106 | ``` 107 | -------------------------------------------------------------------------------- /rules/property-hooks-for-outlets.md: -------------------------------------------------------------------------------- 1 | ## Use property hooks to configure outlets 2 | 3 | ### Description 4 | 5 | Instead of applying style to a `UIView` after the `awakeFromNib()` method, we do it in the `didSet` property of each outlet. 6 | 7 | ### Rationale 8 | 9 | In the traditional way, we would have something like this: 10 | 11 | ```swift 12 | public final class MyView: UIView { 13 | 14 | @IBOutlet weak var myLabel: UILabel! 15 | @IBOutlet weak var myButton: UIButton! 16 | 17 | public override func awakeFromNib() { 18 | super.awakeFromNib() 19 | setMyLabelStyle() 20 | setMyButtonStyle() 21 | ... 22 | } 23 | 24 | } 25 | 26 | private extension MyView { 27 | 28 | private func setMyLabelStyle() { 29 | myLabel.backgroundColor = UIColor.whiteColor() 30 | } 31 | 32 | private func setMyButtonStyle() { 33 | myButton.backgroundColor = UIColor.whiteColor() 34 | myButton.setTitleColor(UIColor.whiteColor(), forState: .Normal) 35 | } 36 | ... 37 | } 38 | ``` 39 | 40 | We face some problems with this approach: 41 | 42 | **1. We need to override the awakeFromNib() method.** 43 | 44 | This isn't really a problem, but it is usually not necessary. 45 | 46 | **2. We need to add a private function for each `IBOutlet`.** 47 | 48 | It often happens that we have to change that outlet, for example `myLabel` to a `UITextField`. Now, we should also change the function `setMyLabelStyle()` to `setMyTextFieldStyle()`. 49 | 50 | **3. The code setting the outlet style is visually far from the outlet itself.** 51 | 52 | It forces us to first find which outlet we want to change, then look for the private function and then change that function. 53 | 54 | **4. The file gets bigger as we keep adding functions.** 55 | 56 | ### Example 57 | 58 | ```swift 59 | public final class MyView: UIView { 60 | 61 | @IBOutlet weak var myLabel: UILabel! { 62 | didSet { 63 | myLabel.backgroundColor = UIColor.whiteColor() 64 | } 65 | } 66 | 67 | @IBOutlet weak var myButton: UIButton! { 68 | didSet { 69 | myButton.backgroundColor = UIColor.whiteColor() 70 | myButton.setTitleColor(UIColor.whiteColor(), forState: .Normal) 71 | } 72 | } 73 | } 74 | 75 | ``` -------------------------------------------------------------------------------- /rules/separate-view-logic.md: -------------------------------------------------------------------------------- 1 | ## Extract view logic from controller 2 | 3 | ### Description 4 | 5 | Instead of having all the outlets and styling logic for the view in its View Controller, we extract all this logic to a different ```UIView``` subclass. This makes controllers smaller and is a much better way of dividing responsibilites. 6 | 7 | 8 | ### Rationale 9 | 10 | A very common problem as applications grow over time is that controllers often end up being quite large and handling a lot of responsibilities. One approach we take at Wolox to avoid this is using the MVVM architecture, thus moving most of the presentation logic out of the View Controller. However, when the view itself is rcomplex, the controller ends up having outlet references to many interface elements and then having to bind them and update them throughout its logic. Therefore, we prefer to have a UIView subclass that has all these references and handles the view logic, together with the styling of the elements. The View Controller still has to pass the elements that it gets from its View Model, but as it only has a reference to the view, it saves it from having to style and manage all these interface elements. 11 | 12 | ### Example 13 | 14 | ```swift 15 | final class UserProfileView: UIView { 16 | 17 | @IBOutlet var nameTextField: UITextField! 18 | @IBOutlet var emailTextField: UITextField! 19 | @IBOutlet var avatarView: UIImageView! 20 | 21 | static func loadFromNib(bundle: NSBundle = NSBundle(forClass: UserProfileView.self)) -> UserProfileView { 22 | let nibName = String(self).componentsSeparatedByString(".").first! 23 | return bundle.loadNibNamed(name, owner: self, options: nil)[0] as! NibLoadableViewType 24 | } 25 | 26 | } 27 | 28 | final class UserProfileController: UIViewController { 29 | 30 | lazy var userProfileView: UserProfileView = UserProfileView.loadFromNib() 31 | 32 | override func loadView { 33 | view = userProfileView 34 | } 35 | 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /rules/table-view-cell-identifier.md: -------------------------------------------------------------------------------- 1 | ## Table view cell identifiers should be inferred from the class name 2 | 3 | ### Rationale 4 | 5 | TODO 6 | 7 | ### Example 8 | 9 | ```swift 10 | protocol IdentifiableCell { 11 | 12 | static var cellIdentifier: String { get } 13 | 14 | } 15 | 16 | extension UITableViewCell: IdentifiableCell { 17 | 18 | static var cellIdentifier: String { return String(self) } 19 | 20 | } 21 | 22 | extension UITableView { 23 | 24 | func registerCell(cellType: IdentifiableCell.Type) { 25 | registerNib(UINib(nibName: cellType.cellIdentifier, bundle: nil), forCellReuseIdentifier: cellType.cellIdentifier) 26 | } 27 | 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /rules/use-extensions-when-conforming-to-protocols.md: -------------------------------------------------------------------------------- 1 | ## Use extensions when conforming to protocols 2 | 3 | ### Description 4 | 5 | When a class must conform to a protocol, for example with UI classes' delegates, we declare all the properties and functions needed in a separate extension which implements that protocol. 6 | 7 | 8 | 9 | ### Rationale 10 | Even if the class is following all other rules and the Single Responsibility Principle, having all its functions declared inside the class make this confusing and difficult to find. The class can have many private and public functions, but if the class must conform to a protocol, this sure is a particular task. When those protocol conforming functions are extracted into a separate extension, whenever someone wants to review how the class manages that task, the only search needed is looking for the extension that implements the protocol. This prevents the person from having to first, search which are the functions the protocol requires and second, search through all the set of functions the class has for those wanted ones. Moreover, it helps break the class's responsabilities into smaller "tasks", making it easier to follow and understand. 11 | 12 | ### Example 13 | 14 | ```swift 15 | public final class SignUpController: UIViewController { 16 | 17 | private lazy var signUpView: SignUpView = .loadFromNib() 18 | 19 | private let _viewModel: SignUpViewModel 20 | 21 | public init(viewModel: SignUpViewModel) { 22 | _viewModel = viewModel 23 | super.init(nibName: nil, bundle: nil) 24 | } 25 | 26 | public override func viewDidLoad() { 27 | super.viewDidLoad() 28 | bindViewModel() 29 | } 30 | 31 | public bindViewModel() { 32 | signUpView.emailTextField.delegate = self 33 | _viewModel.email <~ signUpView.emailTextField.rex_textSignal 34 | signUpView.passwordTextField.delegate = self 35 | _viewModel.password <~ signUpView.passwordTextField.rex_textSignal 36 | 37 | bindButtons() 38 | ... 39 | } 40 | 41 | ... 42 | 43 | } 44 | 45 | extension SignUpController: UITextFieldDelegate { 46 | 47 | public func textFieldShouldReturn(textField: UITextField) -> Bool { 48 | guard validCredetentials() else { 49 | textField.resignFirstResponder() 50 | return true 51 | } 52 | if textField == signUpView.emailTextField { 53 | signUpView.passwordTextField.becomeFirstResponder() 54 | } else { 55 | signUpView.emailTextField.becomeFirstResponder() 56 | } 57 | return true 58 | } 59 | 60 | } 61 | 62 | ``` 63 | -------------------------------------------------------------------------------- /rules/use-lazy-actions.md: -------------------------------------------------------------------------------- 1 | ## Use lazy actions 2 | 3 | ### Rationale 4 | 5 | TODO 6 | 7 | ### Example 8 | 9 | ```swift 10 | final class LoginViewModel { 11 | 12 | public let email = MutableProperty("") 13 | public let password = MutableProperty("") 14 | 15 | private let _credentialsAreValid: AnyProperty 16 | private lazy var logIn: Action = { 17 | return Action(enabledIf: self._credentialsAreValid) { [unowned self] _ in 18 | if let email = Email(raw: self.email.value) { 19 | let password = self.password.value 20 | return self._sessionService.logIn(email,password).observeOn(UIScheduler()) 21 | } else { 22 | return SignalProducer(error: .InvalidCredentials(.None)) 23 | } 24 | } 25 | }() 26 | 27 | init(sessionService: SessionService, credentialsValidator: LoginCredentialsValidator = LoginCredentialsValidator()) { 28 | let isValidEmail = email.signal.map { validateEmail($0) } 29 | let isValidPassword = password.signal.map { validatePassword($0) } 30 | _credentialsAreValid = AnyProperty( 31 | initialValue: false, 32 | signal: combineLatest(isValidEmail, isValidPassword).map { $0 && $1 } 33 | ) 34 | } 35 | 36 | } 37 | ``` 38 | --------------------------------------------------------------------------------