├── assets ├── .DS_Store ├── logo-v1.png ├── xcode-text-settings.png ├── xcode-text-settings-objc.png └── xcode-text-settings-swift.png ├── archive ├── WEEKLY_MEETINGS.md └── BLOG_TOPIC_SUGGESTIONS.md ├── README.md ├── REUSABLE_COMPONENTS.md ├── default.swiftlint.yml ├── COMMUNICATION.md ├── GIT_AND_GITHUB.md ├── BEST_PRACTICES.md └── STYLE_GUIDE.md /assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bakkenbaeck/iOS-handbook/HEAD/assets/.DS_Store -------------------------------------------------------------------------------- /assets/logo-v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bakkenbaeck/iOS-handbook/HEAD/assets/logo-v1.png -------------------------------------------------------------------------------- /assets/xcode-text-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bakkenbaeck/iOS-handbook/HEAD/assets/xcode-text-settings.png -------------------------------------------------------------------------------- /assets/xcode-text-settings-objc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bakkenbaeck/iOS-handbook/HEAD/assets/xcode-text-settings-objc.png -------------------------------------------------------------------------------- /assets/xcode-text-settings-swift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bakkenbaeck/iOS-handbook/HEAD/assets/xcode-text-settings-swift.png -------------------------------------------------------------------------------- /archive/WEEKLY_MEETINGS.md: -------------------------------------------------------------------------------- 1 | We've agreed that we'll have a weekly meeting every Thursday at 09:00. Not all the members have to be present. If we expect everybody to be there, scheduling can get really challenging, and this won't get easier with time, it will only get more difficult as we grow. Also, it is important to keep doing it continuously so it becomes part of our routine. 2 | 3 | This time will be mainly used to: 4 | - Go through the issues and convert them into PRs. 5 | - Close open PRs. 6 | - Discuss something about our projects. 7 | 8 | # 22-12-16 9 | - Created this document 😂. 10 | - We assigned a bunch of issues that will be worked on during the week (#74, #67, #25, #24, #21, #15, #14). 11 | 12 | # 02-02-16 13 | - Talk about Open House 14 | - Talk about hiring 15 | 16 | # 09-02-16 17 | - Talk about finances app navigation architecture 18 | -------------------------------------------------------------------------------- /archive/BLOG_TOPIC_SUGGESTIONS.md: -------------------------------------------------------------------------------- 1 | This is a list of ideas of what we would like to publish. 2 | 3 | - CocoaPods vs Carthage vs Swift Package Manager (SPM). 4 | - What are our experiences with each one? Which do we like best, and why? What are their shortcomings in our experience? 5 | - Dealing with people's names. 6 | - Dealing with names is hard, error-prone, etnocentric and excludes people . It doesn't have to be. 7 | - Dealing with languages and localisation. 8 | - When does localising your app make sense. Is it hard? How hard? How do we do it? Do we do it? 9 | - Stemming, lemmatisation and word separation. 10 | - Stemming, lemmatisation and word separation and three related forms of input normalisation that are often forgotten, misused or unknown to most programmers. It shouldn't be. 11 | - Visual design v. user experience. (?) 12 | - When and why does one superseed the other? What should be the priority? Design is how it works. Making it pretty is a sideshow. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/bakkenbaeck/iOS-playbook/master/assets/logo-v1.png) 2 | 3 | This playbook consists of a set of guidelines that may be useful to you. It is a living document that contains good advice on how to style code, communicate, use git, and more. As a guideline, it is not a rule of law. Being consistent with the repo and your fellow developers takes precedence over this playbook. Also, the playbook can be ignored if a developer believes that adhering to the guidelines would give a worse result. At which point - you may want to consider updating the playbook 😀 4 | 5 | Don't forget to check out our favorite [suite of components and helper methods](http://github.com/usesweet) and the [other B&B iOS repos](https://github.com/bakkenbaeck?language=swift) to see if we've already built something to help with what you're trying to do. 6 | 7 | ### Index 8 | 9 | * [Swift style guide](STYLE_GUIDE.md) 10 | * [Best practices](BEST_PRACTICES.md) 11 | * [Communication](COMMUNICATION.md) 12 | * [Git and GitHub](GIT_AND_GITHUB.md) 13 | * [Reusable components](REUSABLE_COMPONENTS.md) 14 | 15 | ### Maintainers 16 | 17 | * [@DesignatedNerd](https://github.com/designatednerd) 18 | * [@yuliaveres](https://github.com/yuliaveres) 19 | * [@marijnschilling](https://github.com/marijnschilling) 20 | * [@Elland](https://github.com/Elland) 21 | * [@roberthein](https://github.com/roberthein) 22 | 23 | ### License 24 | 25 | [![License: CC BY-SA 4.0](https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg)](http://creativecommons.org/licenses/by-sa/4.0/) 26 | -------------------------------------------------------------------------------- /REUSABLE_COMPONENTS.md: -------------------------------------------------------------------------------- 1 | # Reusable components 2 | 3 | Every agnostic component (UI control, extension, helper, etc) should be created in a separate repository and in Swift. 4 | 5 | Your component should be unit tested and documented. 6 | 7 | ## Table of Contents 8 | 9 | - [Steps to create a component](#steps-to-create-a-component) 10 | - [Steps to ship a component](#steps-to-ship-a-component) 11 | - [Publicizing your component](#publicizing-your-component) 12 | 13 | # Steps to create a component 14 | 15 | - [ ] Use this template: https://github.com/JohnSundell/SwiftPlate - it will automatically add support for CocoaPods, Carthage, and Swift Package Manager. 16 | - [ ] Create a local folder with your project name and run `swiftplate` from there 17 | - [ ] 👶 Author: Your own name 18 | - [ ] 📫 Author email: hello@bakkenbaeck.no 19 | - [ ] 🌍 GitHub URL: https://github.com/bakkenbaeck/ 20 | - [ ] 🏢 Organization Name: Bakken & Bæck 21 | 22 | # Steps to ship a component 23 | 24 | - [ ] Make sure that the README states what makes this component different than the other ones - simply having been made by us is probably not enough. It's very important that your README is 🌟 fabulous 🌟. 25 | - [ ] Provide a super-simple example on how to get the component up and running. 26 | - [ ] If the component is dependent on other frameworks, explain why they're needed and what they do. Don't assume that people know everything. 27 | - [ ] If it's a visual component, include a `.gif` showing how it works or what it does. It helps people understand your component without having to clone, build and run your project. 28 | - [ ] Make sure to have a cool logo 😎 29 | 30 | ## Publicizing your component 31 | 32 | Always express that you are part of a team, use phrases like **"We did this"** rather than **"I did this"**. 33 | 34 | Take the time to compose personal tweets to all your recipients. Copy-pasting one message to everyone makes you sound like a robot and feels spammy -- don't do this. 35 | 36 | - [ ] Submit it to [Cocoa Controls](https://www.cocoacontrols.com/) 37 | - [ ] Make a PR to [iOS Goodies](https://github.com/iOS-Goodies/iOS-Goodies) 38 | - [ ] Submit your component to [Hacker News](https://news.ycombinator.com/). your post should start with **Show HN** 39 | - [ ] Send a tweet from the [@bakkenbaeck](https://twitter.com/bakkenbaeck) account with a tiny summary and attach the logo of the Pod 40 | - [ ] Send a tweet to [@daveverwer](https://twitter.com/daveverwer) of [@iOSDevWeekly](https://twitter.com/iOSDevWeekly) 41 | - [ ] Send a tweet to [@JesseSquires](https://twitter.com/jesse_squires) of [Swift Weekly Brief](https://twitter.com/swiftlybrief) 42 | - [ ] Send a tweet to [@iOSGoodies](https://twitter.com/iOSGoodies) 43 | 44 | -------------------------------------------------------------------------------- /default.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - file_length # Files should not span too many lines - They shouldn't, but we're not enforcing a specific limit on this. 3 | - function_body_length # Functions bodies should not span too many lines - They shouldn't, but we're not enforcing a specific limit on this. 4 | - function_parameter_count # Number of function parameters should be low - They may need to be high for dependency injection. 5 | - identifier_name # Long set of rules about naming - This is too restrictive. 6 | - line_length # Lines should not span too many characters - They shouldn't, but we're not enforcing a specific limit on this. 7 | - nesting # Types should be nested at most 1 level deep, and statements should be nested at most 5 levels deep - This is too restrictive. 8 | - todo # TODOs and FIXMEs should be resolved - They should be eventually, but not at a warning-enforcement level. 9 | - trailing_comma # Trailing commas in arrays and dictionaries should be avoided/enforced - Too nitpicky. 10 | - type_body_length # Type bodies should not span too many lines - They shouldn't, but we're not enforcing a specific limit on this. 11 | - type_name # Various naming restrictions - These are too restrictive. 12 | opt_in_rules: 13 | - contains_over_first_not_nil # Prefer contains over first(where:) != nil 14 | - empty_count # Prefer checking isEmpty over comparing count to zero. 15 | - empty_string # Prefer checking isEmpty over comparing string to an empty string literal. 16 | - explicit_init # Explicitly calling .init() should be avoided. 17 | - fatal_error_message # A fatalError call should have a message. 18 | - first_where # .first(where:) over .filter { }.first in collections. 19 | - implicitly_unwrapped_optional # Implicitly unwrapped optionals should be avoided when possible. 20 | - operator_usage_whitespace # Operators should be surrounded by a single whitespace when they are being used. 21 | - overridden_super_call # Some overridden methods should always call super 22 | - override_in_extension # Extensions shouldn't override declarations. 23 | - private_action # IBActions should be private. 24 | - private_outlet # IBOutlets should be private to avoid leaking UIKit to higher layers. 25 | - private_unit_test # Unit tests should not be marked private because they are silently skipped. 26 | - prohibited_super_call # Some methods should not call super 27 | - redundant_nil_coalescing # nil coalescing operator is only evaluated if the lhs is nil, coalescing operator with nil as rhs is redundant 28 | - switch_case_on_newline # Cases inside a switch should always be on a newline 29 | trailing_whitespace: 30 | ignores_empty_lines: true # don't try to make people delete trailing whitespace from empty lines 31 | private_outlet: 32 | allow_private_set: true # allows private(set) for IBOutlets 33 | cyclomatic_complexity: 34 | ignores_case_statements: true # Don't count `switch` case statements when calculating complexity - this is a trade-off we accept. 35 | excluded: # paths to ignore during linting. Takes precedence over `included`. 36 | - Carthage 37 | - Pods 38 | 39 | ## Rules to consider enabling 40 | # - discouraged_object_literal # Prefer initializers over object literals - If you are using generated code for images and/or colors, you should use that code instead of literals. 41 | -------------------------------------------------------------------------------- /COMMUNICATION.md: -------------------------------------------------------------------------------- 1 | # Communicating with your colleagues 2 | 3 | Within our iOS team, we mostly communicate with each other via the magic of the internet, and that's great! It's easy and very efficient, especially since we're often in different cities and/or countries. 4 | 5 | But online communication can easily result into misunderstandings, which tend to be long, time consuming and inefficient.These guidelines are meant to solve issues in our professional communication, like pull request comments, code review and more thorough discussions. 6 | 7 | Keep them in the back of your head when commenting on GitHub or Slack. 8 | 9 | ## Table of contents 10 | 11 | - [Short replies](#short-replies) 12 | - [Criticizing or asking questions](#criticizing-or-asking-questions) 13 | - [Giving advice or tips](#giving-advice-or-tips) 14 | - [Clarifications and disagreements](#clarifications-and-disagreements) 15 | 16 | ## Short replies 17 | 18 | When you write a reply of less than ~5 words, have an alarm bell in the back of your head 🔔. Is it just tacit agreement? Then it’s probably fine! “Great job”, “Couldn’t have put it better”, “totes awesome!” are all fine. 19 | 20 | In every other case though: 🚨! It's good practice to elaborate. It may seem inefficient to be so verbose in our replies, but it will avoid misunderstandings, which can result in far greater inefficiency. 21 | 22 | ## Criticizing or asking questions 23 | 24 | Start by repeating **what** you are replying to in your own words. This forces you to indulge yourself into the perspective of the other, which creates a greater understanding of the issue both for yourself and the other when reading your reply. 25 | 26 | ### Avoid: 27 | 28 | - Don't use optional here. 29 | - What’s your suggestion? 30 | 31 | ### Prefer: 32 | 33 | - **I see you are using an optional as an array of User object in the example above**, have you considered having that property default to an empty array? 34 | - **Okay, so you say using a collectionView here would be over-engineering**, how would you then solve this problem instead? 35 | 36 | ## Giving advice or tips 37 | 38 | Explain why you think this might be helpful in this particular case. Don’t just prescribe, but try to make your case for a particular approach. 39 | 40 | ### Avoid: 41 | 42 | - Why not use a CollectionView for this? 43 | - Read this article: 44 | 45 | ### Prefer: 46 | 47 | - A collection view might be helpful in this case, **because it would take care of the layout for you. Otherwise you’ll be redoing the same work, implementing your own lightweight collection view instead.** 48 | - This article might be interesting for you, **as it explains the threats of storing API keys in plaintext.** 49 | 50 | ## Clarifications and disagreements 51 | 52 | If you feel someone’s reaction is not clear enough (i.e. someone’s reaction confuses you, or even agitates you), ask for a clarification, or if the person could rephrase that, if the meaning isn’t clear. 53 | 54 | Try to keep in mind that in written communication, it’s really easy to misread something or to interpret something out of context. Sometimes something was poorly phrased. Sometimes we’re switching between contexts too often, and end up being shorter than we should. 55 | 56 | However, don't be afraid to let your colleagues know if you feel they're not following these guidelines, or if you're feeling attacked. All humans can use reminders from time to time to be a little nicer to each other. 57 | 58 | Keep in mind that you are often reviewing or discussing something that someone else has worked on really hard, so always be respectful. [These guidelines for giving feedback on pull requests are a good reference](GIT_AND_GITHUB.md#reviewing-pull-requests). 59 | 60 | If following these suggestions doesn't clear things up, consider a call to solve the issue if the matter is pressing, or wait for the next iOS meeting to continue discussing it. 61 | 62 | For the long term, look at this document together to see what could be improved in the future. If you realize something critical is missing in these guidelines, please add to it. -------------------------------------------------------------------------------- /GIT_AND_GITHUB.md: -------------------------------------------------------------------------------- 1 | # Git & GitHub Conventions 2 | 3 | These conventions apply to our open-source, internal, and client projects. 4 | 5 | ## Commits 6 | 7 | The `master` branch is the stable branch. It's also used for development purposes, as to avoid having `dev`, `development`, `staging`, `wip` and others. 8 | 9 | You might be wondering: why only `master`? What if I have to rollback to apply a fix? Well, if you find a bug in a previous release, you can just, checkout the tag, fork it, fix it, deploy the new build, and finally, submit a PR to merge it with `master` to the sound of Daft Punk. 10 | 11 | Prefer concise commits over gigantic ones. When writing a concise commit message is difficult, it may indicate too many unrelated changes. 12 | 13 | ``` 14 | // Preferred 15 | Add employee avatar in list of employees 16 | 17 | // Not preferred 18 | Added image view to display image for employee in employees table view controller. 19 | ``` 20 | 21 | If you need to elaborate more, you can do so in the commit description. 22 | 23 | ## Branches 24 | 25 | Branches are awesome. We use `feature/`, `fix/`, `improve/` and `refactor/` for pseudo-namespacing: 26 | 27 | ``` 28 | feature/new-feature 29 | fix/make-it-work 30 | improve/make-it-better 31 | refactor/make-it-awesome 32 | ``` 33 | 34 | Rarely, it's okay to just push to `master` if it's a quick fix and you really need it out the door. Usually, you make a pull request to `master` or to a long-running `feature` branch instead. 35 | 36 | ### Pull requests 37 | 38 | Pull request descriptions should be concise and well written. The reviewer should be able to copy this description straight into the release notes, instead of figuring out what changed or was fixed. 39 | 40 | Besides, a description with more information could be helpful, for example: 41 | 42 | - If it's a static UI, a screenshot will do. 43 | - If it's an interaction, a `gif` would help a lot. Good tools to make `gif`s include [Licecap](http://www.cockos.com/licecap/), [GIPHY Capture](https://itunes.apple.com/us/app/giphy-capture-the-gif-maker/id668208984?mt=12), and [GIF Brewery](http://gifbrewery.com/). 44 | - If it's a bug, steps to reproduce are very useful to help understanding context. 45 | 46 | Before merging a pull request, your code has to be reviewed, so that we can learn from you, help you find mistakes, and/or post a sufficient amount of gifs. The reviewing process is important. It is better for you to have another person backing you up. More eyes on code mean fewer bugs and better consistency throughout. 47 | 48 | 49 | #### Reviewing pull requests 50 | 51 | As a reviewer, you should ideally be a core team member and have enough context to make a thorough review of the committed code, just by reading it. However, there are times when the pull request is a bit on the large side, or it is referencing existing code not found in the PR, or you for some other reason do not have a good enough context to review anything else than code style and typos. In these cases, we encourage you to physically (or virtually) approach the PR submitter and go through the pull request together. 52 | 53 | Be warned, though: By doing this, you will both need to explain your thoughts and discuss other alternatives. Of course, this means you are putting yourself at risk of sharing your knowledge and/or learning some new stuff, so please be careful not to end up being even more awesome than you already are. 54 | 55 | As a reviewer your job is to be the extra pair of eyes, so the code will get twice as good. You'll look at things such as code style and structure. You'll discuss things that you might've done differently, point out possible more elegant solutions based on something you've learned, and so on. The goal here is to make the whole team better in the process. 56 | 57 | Keep in mind that you are reviewing something that someone else has worked on really hard, so always be respectful. These guidelines for giving feedback are a good reference: 58 | 59 | - Familiarize yourself with the context of the issue. 60 | - If you disagree strongly, consider giving it a few minutes before responding; think before you react. Try to empathize. 61 | - Ask, don’t tell. (“What do you think about trying…?” rather than “Don’t do…”) 62 | - Explain your reasons why code should be changed. (Not in line with the style guide? A personal preference?) 63 | - Offer ways to simplify or improve code. 64 | - Avoid using derogatory terms, like “stupid”, when referring to the work someone has produced. Don’t shame, no one started out knowing. 65 | - Be humble. (“I’m not sure, let’s try…”) 66 | - Never deal in absolutes. Unless needed. (“We should never store sensitive data in plain text.”) 67 | - Aim to develop professional skills, group knowledge and product quality, through group critique. That goes both way: try to be open and welcoming to criticism. 68 | - Be aware of negative bias with online communication. (If content is neutral, we assume the tone is negative.) Try to use positive language instead. 69 | - Use emoji to clarify tone. Compare “ok” to “ok 😃.” 70 | 71 | [Based on GitHub's "How to write the perfect pull request".](https://github.com/blog/1943-how-to-write-the-perfect-pull-request) 72 | 73 | If everything is fine, feel free to merge the pull request. Avoid merging before receiving a feedback. If several developers are involved in the project, one confirmation should be enough. You can always submit subsequent pull requests or file an issue after the fact. 74 | 75 | After you have merged, you should delete the branch and smile. The smile is a critical part of the process, so don't forget this. 76 | 77 | #### My pull request depends on another pending pull request. What to do? 78 | 79 | If your next pull request is really that intimately related to your last, 80 | consider to continue working on it instead of opening another. 81 | 82 | If it really makes sense to open another, it's okay to base your next pull request on your unmerged branch, just make sure to make it clear in the PR description. Note in the description that the PR depends on its parent PR, and add a link to the pending parent PR. 83 | 84 | Finally, make sure to merge any changes you've made to its parent during its review before seeking final review of your child PR. 85 | 86 | [_This is partly based on Hyper's Git and GitHub guide._](https://github.com/hyperoslo/playbook/blob/master/GIT_AND_GITHUB.md) 87 | 88 | ## Releases 89 | 90 | Steps for releasing a new version of your app: 91 | 92 | 1. Switch to the `master` branch. 93 | 2. Bump the projects version number (make sure to use [semantic versioning](http://semver.org/)) and build number. 94 | 3. Create the archive. 95 | 4. Upload the archive to iTunes Connect for submission to TestFlight or App Review. 96 | 4. Create a [release on GitHub](https://help.github.com/articles/creating-releases/). Remember to mark it as a `pre-release` if needed. 97 | -------------------------------------------------------------------------------- /BEST_PRACTICES.md: -------------------------------------------------------------------------------- 1 | # Best Practices 2 | 3 | These are things we've found to be helpful in creating readable, maintainable code rather than hard requirements. Hard requirements live in the [Style Guide](STYLE_GUIDE.md) 4 | 5 | ## Table of contents 6 | 7 | * [Xcode](#xcode) 8 | * [Version](#version) 9 | * [Project structure](#project-structure) 10 | * [Versioning](#versioning) 11 | * [Class structure](#class-structure) 12 | * [Comments](#comments) 13 | * [Extension method naming](#extension-method-naming) 14 | * [Blocks/closures vs. delegates](#blocks-closures-vs-delegates) 15 | * [View controllers](#view-controllers) 16 | * [Property observing](#property-observing) 17 | * [Networking](#networking) 18 | 19 | ## Xcode 20 | 21 | ### Version 22 | 23 | The recommended version of Xcode (and Swift) for all purposes is the current available [in the App Store](https://itunes.apple.com/no/app/xcode/id497799835?mt=12). 24 | 25 | Make sure to coordinate with your project team when upgrading to a new version, particularly if a new version of Swift is involved. 26 | 27 | ### Project structure 28 | 29 | The physical files should be kept in sync with the Xcode project files in order to avoid file sprawl. Any Xcode groups created should be reflected by folders in the filesystem. Code should be grouped by type and feature for greater clarity. 30 | 31 | Make sure to use an [app-template](https://github.com/bakkenbaeck/app-template) for consistency. 32 | 33 | ### Warnings 34 | 35 | Enable warnings by adding `-Weverything` to your Build Settings under "Other Compiler Flags". If you need to ignore a specific warning you can use [Clang's pragma feature](http://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-via-pragmas) or add `-Wno-warning-to-be-disabled` (for example `-Wno-gnu-conditional-omitted-operand`). 36 | 37 | ## Versioning 38 | 39 | To version our open source projects we use [semantic versioning](http://semver.org/), and it's important that minor releases are backwards compatible otherwise don't feel shy to make it a major release. 40 | 41 | When making backwards compatible changes, flag your old APIs as deprecated like this: 42 | 43 | ```swift 44 | @available(*, deprecated: 4.3.0, message: "Use `objectAt(index index: Int)` instead") public func objectAtIndex(index: Int) 45 | ``` 46 | 47 | When it comes to apps, patch releases are bug fixes, minor releases are small new features and major releases are re-designs or big features. 48 | 49 | ## Class structure 50 | 51 | If you are adding a class and several extensions to that class in one file, reading the class becomes simpler if the extensions are in the file below the class. 52 | 53 | Generally, a class file should look like this: 54 | 55 | ``` 56 | class { 57 | vars 58 | init 59 | funcs 60 | } 61 | 62 | extensions 63 | ``` 64 | 65 | ## Comments 66 | 67 | When they are needed, comments should be used to explain **why** a particular piece of code does something instead of **what**. Any comments must be kept up-to-date or deleted. 68 | 69 | **Preferred:** 70 | 71 | ```swift 72 | func scrollToBottom() { 73 | /* 74 | Workaround: So far this is the scroll-to-bottom method that worked best, 75 | with proper animation, and no issues so far, regardless of scrollview content size. 76 | If this looks and feels like a hack, it's because it kinda is. 77 | By creating a rect that's 1x1, pointed at the bottom-right side of the scrollview's content 78 | and telling it to scroll there, regardless of insets and offsets, it will scroll to the very bottom. 79 | */ 80 | let contentSize = collectionView.contentSize 81 | let bottomRect = CGRect(x: contentSize.width - 1, y: contentSize.height - 1, width: 1, height: 1) 82 | let visibleRect = collectionView.layer.visibleRect 83 | if !visibleRect.intersects(bottomRect) { 84 | collectionView.scrollRectToVisible(bottomRect, animated: true) 85 | } 86 | } 87 | ``` 88 | 89 | **Not Preferred:** 90 | 91 | ```swift 92 | // Scrolls to the bottom of the view 93 | func scrollToBottom() { 94 | let contentSize = collectionView.contentSize 95 | let bottomRect = CGRect(x: contentSize.width - 1, y: contentSize.height - 1, width: 1, height: 1) 96 | let visibleRect = collectionView.layer.visibleRect 97 | if !visibleRect.intersects(bottomRect) { 98 | collectionView.scrollRectToVisible(bottomRect, animated: true) 99 | } 100 | } 101 | ``` 102 | 103 | ## Extension method naming 104 | 105 | Watch out for potential method name collisions with Swift extensions of Objective-C classes. [Peter Steinberger goes into the maddening details of why here](https://pspdfkit.com/blog/2016/surprises-with-swift-extensions/), but when adding an extension to an Objective-C class, it can be helpful to add an `@objc` wrapper with a prefix for the compiler. 106 | 107 | **Preferred:** 108 | 109 | ```swift 110 | extension UIViewController { 111 | 112 | @objc(myapp_foo) 113 | func foo() { 114 | // Do something view controllery 115 | } 116 | } 117 | ``` 118 | 119 | **Not preferred**: 120 | 121 | ```swift 122 | extension UIViewController { 123 | 124 | func foo() { 125 | // Do something view controllery 126 | // At least until Apple implements their own foo() 127 | } 128 | } 129 | ``` 130 | 131 | ## Blocks/closures vs delegates 132 | 133 | Choosing when to use a block/closure vs. a delegate method is usually is a simple decision, but if you're having trouble deciding here are some reminders on what does what. 134 | 135 | **When To Use Blocks/Closures:** 136 | 137 | - Asynchronous (For example: networking operations) 138 | - User inputs with multiple options (For example: `UIAlertController`'s assorted buttons) 139 | - Data source driven inputs (For example: A table items with action blocks that were defined in the data source) 140 | - If there’s no tracked state, or if the state is defined in the same method 141 | 142 | **When To Use Delegates** 143 | 144 | - Synchronous (For example: buttons actions in views that should perform on their parents) 145 | - Shouldn't return values 146 | - Provides control over performing an action (For example: UITextField's shouldEndEditing) 147 | - User input with one action (For example: buttons actions in views that should perform on their parents) 148 | - If tracked state is shared (if state is stored in a property or a constant) 149 | 150 | ## View controllers 151 | 152 | ### Presenting and dismissing View Controllers 153 | 154 | - It's better practice to call `dismissViewControllerAnimated:completion:` in the `UIViewController` that did the presenting, not in the `UIViewController` that was presented. 155 | 156 | [More information on this blog post.](https://sandofsky.com/blog/never-reach-up.html) 157 | 158 | ## Property observing 159 | 160 | When using Property Observers make sure to not over-reload the UI. It's common that when using property observers any change will update some part of the UI. 161 | 162 | For example: 163 | 164 | ```swift 165 | // PhotoViewerController 166 | var photo: Photo? { 167 | didSet { 168 | guard let viewerItem = viewerItem else { return } 169 | // Do something with photo, maybe download and so on 170 | } 171 | } 172 | ``` 173 | 174 | The problem with this logic is that if for some reason the caller repeatedly sets the same photo, this can have awful performance issues. 175 | 176 | If the `PhotoViewerController` is handling property observation, it should also handle the cases where the new introduced value is the same as the one that currently exists. 177 | 178 | There are two ways to solve this issue: 179 | 180 | ### Version A: 181 | 182 | One solution would be that the API user (the abuser) makes sure that they don't set a photo item if it has already been set. 183 | 184 | ```swift 185 | // ViewerController (A horizontal array of PhotoViewerController) 186 | let photoViewerController = cachedPhotoViewerControllers.objectForKey(photo.id) 187 | if photoViewerController.photo?.id != photo.id { 188 | photoViewerController.photo = photo 189 | } 190 | ``` 191 | 192 | ### Version B (Recommended): 193 | 194 | The other solution would be that the `PhotoViewerController` takes care of this since it's the one that decided to do property observing, instead of having a separate method to trigger this side effect (downloading photo). 195 | 196 | ```swift 197 | // PhotoViewerController 198 | var photo: Photo? { 199 | didSet { 200 | guard let photo = photo else { return } 201 | 202 | if photo.id != oldValue?.id { 203 | // Do something with photo, maybe download and so on 204 | } //else, the photo has remained the same. 205 | } 206 | } 207 | ``` 208 | 209 | ## Networking 210 | 211 | Completion blocks in networking calls should be returned in the main thread. This helps avoid needing to deal with threading outside of the networking stack. 212 | 213 | Completion blocks should contain the `error` instead of success/failure blocks. 214 | 215 | Use a simple NSURLSession wrapper to make things simpler. [Teapot](https://github.com/bakkenbaeck/Teapot) is a good candidate for this. 216 | -------------------------------------------------------------------------------- /STYLE_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Swift style guide 2 | 3 | Our overarching goals are conciseness, readability, simplicity, and making as much of this style guide robot-enforcable as possible. 4 | 5 | Code is read far more often than it's written. Clarity at the point of reading is always better than saving a few keystrokes. 6 | 7 | The items in this style guide are required. For more general good ideas and practical advice, please consult our [Best Practices](BEST_PRACTICES.md). 8 | 9 | * [Linting](#linting) 10 | * [Naming](#naming) 11 | * [Class prefixes](#class-prefixes) 12 | * [Spacing and indentation](#spacing-and-indentation) 13 | * [Protocol conformance](#protocol-conformance) 14 | * [View layout](#view-layout) 15 | * [Lazy loading](#lazy-loading) 16 | * [Computed properties](#computed-properties) 17 | * [Function declarations](#function-declarations) 18 | * [Closure expressions](#closure-expressions) 19 | * [Types](#types) 20 | * [Optionals and force-unwrapping](#optionals-and-force-unwrapping) 21 | * [Struct initializers](#struct-initializers) 22 | * [Type inference](#type-inference) 23 | * [Syntactic sugar](#syntactic-sugar) 24 | * [Semicolons](#semicolons) 25 | * [Resources](#resources) 26 | * [Image assets](#image-assets) 27 | * [Localized strings](#localized-strings) 28 | * [Commented code](#commented-code) 29 | 30 | 31 | ## Linting 32 | 33 | For code lint we use [SwiftLint](https://github.com/realm/SwiftLint). Our current `.swiftlint.yml` file is available [here](default.swiftlint.yml). 34 | 35 | Most rules enabled there are for formatting purposes, but there are a few which warn about common issues like forgetting to call `super` in methods, or simple performance tweaks like using `.first(where:)` instead of `.filter { }.first { }`. 36 | 37 | Do not merge PRs which have SwiftLint warnings. 38 | 39 | To automate this process at PR time, you can use [Danger](http://danger.systems/ruby/) and the `Danger-Swiftlint` plugin in [Swift](https://github.com/ashfurrow/danger-swiftlint) or in [Ruby](https://github.com/ashfurrow/danger-ruby-swiftlint) to have a bot that automatically comments on code with violations. Instructions for setting up Danger and its associated bots are [here](http://danger.systems/guides/getting_started.html). 40 | 41 | ## Naming 42 | 43 | Use descriptive names with `CamelCase` for classes, methods, variables, etc. 44 | 45 | Class names should be capitalized. Method names, variables and constants should start with a lower case letter. 46 | 47 | Avoid underscores except where the name of something is used to access other things (such as generated code or `CodableKeys` enums). 48 | 49 | When in doubt, look at how Xcode lists the method in the jump bar – our style here matches that. 50 | 51 | ![Methods in Xcode jump bar](https://raw.githubusercontent.com/raywenderlich/swift-style-guide/master/screens/xcode-jump-bar.png) 52 | 53 | ### Class prefixes 54 | 55 | Do not add prefixes to your Swift types. They are not necessary due to module-level namespacing. 56 | 57 | ## Spacing and indentation 58 | 59 | * Indent using 4 spaces rather than tabs. This should be configured on the project. 60 | 61 | ![Xcode indent settings](https://raw.githubusercontent.com/bakkenbaeck/iOS-playbook/master/assets/xcode-text-settings-swift.png) 62 | 63 | * Method braces and other braces (`if`/`else`/`switch`/`while` etc.) always open on the same line as the statement but close on a new line. 64 | * There should be exactly one blank line between methods to aid in visual clarity and organization. 65 | 66 | ## Protocol conformance 67 | 68 | When adding protocol conformance to a class, prefer adding a separate class extension for the protocol methods rather than adding the protocol conformances to the primary class wherever possible. 69 | 70 | **Preferred:** 71 | 72 | ```swift 73 | class MyViewcontroller: UIViewController { 74 | // class stuff here 75 | } 76 | 77 | extension MyViewcontroller: UITableViewDataSource { 78 | // table view data source methods 79 | } 80 | 81 | extension MyViewcontroller: UIScrollViewDelegate { 82 | // scroll view delegate methods 83 | } 84 | ``` 85 | 86 | **Not Preferred:** 87 | 88 | ```swift 89 | class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate { 90 | // all methods 91 | } 92 | 93 | // MARK: - Data download 94 | extension MyViewController { 95 | } 96 | ``` 97 | 98 | ## Lazy loading 99 | 100 | When creating models with properties that require setup or configuration, it's better to use lazy loading instead of adding the logic to the `viewDidLoad` or `init` methods. 101 | 102 | **Preferred:** 103 | 104 | ```swift 105 | class RecipeCell: UITableViewCell { 106 | private lazy var label: UILabel = { 107 | let label = UILabel() 108 | label.textColor = .red 109 | 110 | return label 111 | }() 112 | 113 | init() { 114 | super.init() 115 | contentView.addSubview(label) 116 | } 117 | ... 118 | ``` 119 | 120 | **Not preferred:** 121 | 122 | ```swift 123 | class RecipeCell: UITableViewCell { 124 | private let label: UILabel 125 | 126 | init() { 127 | label = UILabel() 128 | label.textColor = .red 129 | 130 | super.init() 131 | contentView.addSubview(label) 132 | } 133 | ... 134 | ``` 135 | 136 | ## View layout 137 | 138 | Views should be laid out using Auto Layout. If setting up views programmatically, we recommend using [TinyConstraints](https://github.com/roberthein/TinyConstraints) to make your Auto Layout code easier to both write and read. 139 | 140 | If you don't want to or can't use a library, remember to use the highest layer of abstraction available to you, such as `NSLayoutAnchor` or `UIStackView`, in order to reduce boilerplate. 141 | 142 | Use either single (e.g., `addSubviewsAndConstraints()`) or clearly named multiple (e.g., `setupTextFields()`, `setupButtons()`) layout methods to keep your `viewDidLoad` or `init` methods from being painfully long. 143 | 144 | **Preferred:** 145 | 146 | ```swift 147 | override init(frame: CGRect) { 148 | super.init(frame: frame) 149 | // do general setup things 150 | addSubviewsAndConstraints() 151 | } 152 | 153 | func addSubviewsAndConstraints() { 154 | addSubview(label) 155 | NSLayoutConstraint.activate([ 156 | label.topAnchor.constraint(equalTo: self.topAnchor), 157 | ... 158 | ``` 159 | 160 | **Not preferred:** 161 | 162 | ```swift 163 | override init(frame: CGRect) { 164 | super.init(frame: frame) 165 | // do general setup things 166 | addSubview(label) 167 | NSLayoutConstraint.activate([ 168 | label.topAnchor.constraint(equalTo: self.topAnchor), 169 | ... 170 | ``` 171 | 172 | ## Computed properties 173 | 174 | If a computed property is read-only, omit the `get` clause. The `get` clause is required only when a `set` clause is provided. 175 | 176 | **Preferred:** 177 | 178 | ```swift 179 | var diameter: Double { 180 | return radius * 2.0 181 | } 182 | ``` 183 | 184 | **Not Preferred:** 185 | 186 | ```swift 187 | var diameter: Double { 188 | get { 189 | return radius * 2.0 190 | } 191 | } 192 | ``` 193 | 194 | ## Closure expressions 195 | 196 | Use trailing closure syntax wherever possible. Keep the `[weak self] parameterOne, parameterTwo in` on the same line as the opening brace of the closure. 197 | 198 | Give incoming closure parameters descriptive names: 199 | 200 | ```swift 201 | return SKAction.customActionWithDuration(effect.duration) { node, elapsedTime in 202 | // more code goes here 203 | } 204 | ``` 205 | 206 | Using indexed parameters is permissible, but try to limit it to one-liners, and have clearly named variables with the result of the operation: 207 | 208 | **Preferred:** 209 | 210 | ```swift 211 | let adminUsers = users.filter { $0.isAdmin } 212 | let adminUsernames = adminUsers.map { $0.username } 213 | let sortedUsernames = adminUsernames.sort { $0 < $1 } 214 | ``` 215 | 216 | **Not Preferred** 217 | 218 | ```swift 219 | users.filter { $0.isAdmin } 220 | .map { $0.username } 221 | .sort { $0 < $1 } 222 | ``` 223 | 224 | Be careful getting too happy with `$0`-style parameter usage - make sure that other people (including Future You) can understand exactly what that variable represents when they read your code. 225 | 226 | ## Types 227 | 228 | Always use Swift's native types when available. Swift offers bridging to Objective-C so you can still use the full set of methods as needed. 229 | 230 | **Preferred:** 231 | 232 | ```swift 233 | let width = 120.0 // Double 234 | let widthString = (width as NSNumber).stringValue // String 235 | ``` 236 | 237 | **Not Preferred:** 238 | 239 | ```swift 240 | let width: NSNumber = 120.0 // NSNumber 241 | let widthString: NSString = width.stringValue // NSString 242 | ``` 243 | 244 | ## Optionals and force-unwrapping 245 | 246 | Declare variables and function return types as optional with `?` where a nil value is acceptable. 247 | 248 | Avoid Implicitly Unwrapped Optionals (i.e., variables declared with an `!`) unless using `IBOutlets` that should never be `nil` after the nib or storyboard has loaded, or unless you have a very clear reason to do so you can justify when disabling the IUO warning in SwiftLint. 249 | 250 | There's a reason `!` has the informal nickname of "the crash operator": If what you think is going to be there is not there, your entire application will crash. 251 | 252 | ### Struct initializers 253 | 254 | Use the native Swift struct initializers rather than the legacy CGGeometry constructors. 255 | 256 | **Preferred:** 257 | 258 | ```swift 259 | let bounds = CGRect(x: 40.0, y: 20.0, width: 120.0, height: 80.0) 260 | var centerPoint = CGPoint(x: 96.0, y: 42.0) 261 | ``` 262 | 263 | **Not Preferred:** 264 | 265 | ```swift 266 | let bounds = CGRectMake(40.0, 20.0, 120.0, 80.0) 267 | var centerPoint = CGPointMake(96.0, 42.0) 268 | ``` 269 | 270 | Prefer the struct-scope constants `CGRect.infiniteRect`, `CGRect.nullRect`, etc. over global constants `CGRectInfinite`, `CGRectNull`, etc. For existing variables, you can use the shorter `.zeroRect`. 271 | 272 | ## Type inference 273 | 274 | Prefer compact code and let the compiler infer the type for a constant or variable. 275 | 276 | **Preferred:** 277 | 278 | ```swift 279 | let message = "Click the button" 280 | var currentBounds = computeViewBounds() 281 | ``` 282 | 283 | **Not Preferred:** 284 | 285 | ```swift 286 | let message: String = "Click the button" 287 | var currentBounds: CGRect = computeViewBounds() 288 | ``` 289 | 290 | **NOTE**: Following this guideline means picking descriptive names is even more important than before. 291 | 292 | ## Syntactic sugar 293 | 294 | Prefer the shortcut versions of type declarations over the full generics syntax. 295 | 296 | **Preferred:** 297 | 298 | ```swift 299 | var deviceModels: [String] 300 | var employees: [Int: String] 301 | var faxNumber: Int? 302 | ``` 303 | 304 | **Not Preferred:** 305 | 306 | ```swift 307 | var deviceModels: Array 308 | var employees: Dictionary 309 | var faxNumber: Optional 310 | ``` 311 | 312 | ## Semicolons 313 | 314 | Do not use semicolons at the end of a line - they are no longer required. 315 | 316 | Do not write multiple statements on a single line separated with semicolons. 317 | 318 | ## Resources 319 | 320 | Use extensions or wrapper classes for accessing elements of asset catalogs, custom colors and fonts. It helps to avoid the error-prone practice of hard-coding strings into your code. 321 | 322 | Using generated code for this wherever possible is even better, since if an element you were previously accessing was removed, when the code is regenerated, the build will fail at compile time instead of runtime. 323 | 324 | Use a `Theme.swift` that contains extensions for the following classes which will assist in theming your app: 325 | 326 | - `UIColor` wrappers 327 | - `CGFloat` sizes for fonts and margins 328 | - `UIFont` wrappers 329 | 330 | Work with the designer on your project to come up with a common naming scheme for colors to make picking colors, fonts, and margins out of a design file easier. 331 | 332 | ### Image assets 333 | 334 | Images should live in `Images.xcassets`. They don't need to be grouped in any way unless you find that helpful for organization. 335 | 336 | Images should be named consistently within an app to preserve organization and developer sanity. It is strongly suggested to append the state name to images for non-default states. 337 | 338 | Images should be named in either `camelCase` or `lower_snake_case` to facilitate code generation based on the image names. Avoid using hyphens within names, as it messes up code completion and generation. 339 | 340 | Whatever naming style you choose, pick a style and stick to it per project. 341 | 342 | **Examples:** 343 | 344 | - `ic_refresh` and `ic_refresh_selected` for a refresh icon and its selected state used in multiple places throughout an app 345 | - `articleNavigationBar` and `articleNavigationBarSelected` for something used in one specific place. 346 | 347 | ### Localized strings 348 | 349 | Any strings visible to the user must be localized using `NSLocalizedString` or something wrapping it. It is way, way easier to set up all strings like this from the beginning than to add this feature later. 350 | 351 | Don't use the developer-language content of the string as the localized string's key, because if you do, you then have to update the copy in multiple places. 352 | 353 | Use `lower_snake_case` for localized string keys to both facilitate code generation and to make it extremely obvious when there is an untranslated user-facing string, since by default if a value does not exist for a key in `Localizable.strings`, the key itself is returned. 354 | 355 | ```swift 356 | // ProfileViewController.swift 357 | titleLabel.text = NSLocalizedString("profile_title", "Title of the profile page") 358 | 359 | // Localizable.strings 360 | /* Title of the profile page */ 361 | "profile_title" = "Your Profile"; 362 | ``` 363 | 364 | **Not preferred:** 365 | 366 | ```swift 367 | // ProfileViewController.swift 368 | titleLabel.text = NSLocalizedString("Your Profile", "Your Profile") 369 | 370 | // Localizable.strings 371 | /* Your Profile */ 372 | "Your Profile" = "Your Profile"; 373 | ``` 374 | 375 | Some guidelines: 376 | 377 | * Remember that the description is used to tell translators what the purpose of a key is, not what it contains in the developer's language. 378 | * Using a wrapper class (especially a code-generated one) allows easy reuse of copy both in multiple places in the app, as well as in UI tests. 379 | * If you are including support for Accessibility, ensure all strings which will be read by VoiceOver or assistive devices are also localized. 380 | 381 | ## Commented code 382 | 383 | Do not merge commented out code. Any code which is commented out when a PR is otherwise ready to be merged should be deleted. We can always go back and find the deleted code with version control if needed. 384 | 385 | 386 | --------------------------------------------------------------------------------