Swift - Table of contents

Variables and functions

Naming Conventions

  • All variable, class and function names should be descriptive and self-documenting.
  • Always use camelCase instead of snake_case
  • Classes, constants, and enums should start with an initial capital, all others should start lowercase
  • Prefer named parameters for functions and init methods, unless the context is clear from the method name.
  • Follow the Apple convention of referring to the first parameter in the method name
let MaximumWidgetCount = 100

class WidgetContainer {
	var widgetButton: UIButton
	let widgetHeightPercentage = 0.85
}

enum Directions {
  case North, East, South, West
}

func dateFromString(dateString: NSString) -> NSDate
dateFromString("2014-03-14")

func convertPointAt(column: Int, row: Int) -> CGPoint
convertPointAt(column: 42, row: 13)

Use of Self

To keep code clear and concise, avoid using self to access properties or invoke methods. The exception to this is when inside a closure, or when differentiating between property names and arguments in initializers. The compiler will complain when self is not used inside of a closure.

class History {
    var events: [Event]

	init(events: [Event]) {
        self.events = events
    }

    var whenVictorious: (() -> ()) {
        return {
            self.rewrite()
        }
    }

    func rewrite() {
        events = []
    }
}

Types

  • Always use Swift's native types when possible, however, Swift offers bridging to Objective-C so the full set of methods is available if needed.
  • When specifing a type of an identifier, always put the colon immediately after the identifier, followed by a space and then the type name.
  • Always prefer implicit type declarations. The type can be explicitly defined by a type alias if needed.
  • Prefer the shortcut versions of type declarations over the full generics syntax.
//bad
let width: NSNumber = 120.0
//good
let width = 120.0

var submitButton: UIButton
let capitals = [Sweden: Stockholm]

//bad
let capitals: Dictionary<Country, City>
//good
let capitals: [Country: City]

Constants

Prefer constant declarations with let over variable declarations with var wherever possible. Only use var when you know the value might change.

It may help to define everything with let and change it to var when the compiler complains.

//This button needs to be a var because it is instantiated from the storyboard, and may change
@IBOutlet weak var submitButton: UIButton!

//The view height should be constant, so it is defined with let
let viewHeight = view.frame.size.height

//Because UILabel represents a class object, it's properties can be modified even if the object itself is defined with let
let label = UILabel()
label.text = "Hello World!"

Optionals

  • Declare variables and function return types as optional with ? where a nil value is acceptable
  • Avoid force-unwrapping optionals. Optionals exist to add type safety and make validation easy and enforced.
  • Use implicitly unwrapped types declared with ! only for instance variables that you know will be initialized later before use, such as subviews that will be set up in viewDidLoad, variables that will be setup in an init method, or @IBOutlets that will be initialized by the storyboard.
  • When accessing an optional value, prefer optional chaining if the value is only used once, or if there are many optionals in the chain. If it is more convenient to unwrap once and use multiple times, then use optional binding.
  • Avoid naming an optional variable or property like optionalString or maybeView because there optional-ness is already in the type declaration
  • Guard statements can also be used to unwrap optionals.
var textContainer: TextContainerView?

//Optional chaining
textContainer?.textLabel?.setNeedsDisplay()

//Optional binding
if let textContainer = textContainer {
  // do many things with textContainer
}

//Optional binding with a guard
guard let textContainer = textContainer else { return }

Comments

  • When they are needed, use comments to explain why a particular piece of code does something. Comments must be kept up-to-date.
  • Avoid block comments inline with code, as the code should be as self-documenting as possible.
  • Block comments should come before custom methods to generate documentation.
  • Shorter description style documentation comments can also be used for adding documentation to functions.
/**
Description of what the method does

- parameter paramaterName: Description of the parameter
- returns: Description of what the method returns
*/
func someMethod(parameterName: AnyType?) -> returnValueName: AnyType? {
  ...
}

///Description of some function (notice the three slashes)
func someFunction() {
}

MARK:

Use //MARK:s to categorize similar methods into functional groupings and protocol implementations

Notice that there are 0 blank lines after a //MARK: declaration, 2 blank lines before the //MARK:, and 1 blank line between methods.

A hyphen after the MARK: will cause Xcode to draw a seperator line in the jump bar. Use this to seperate very different sections of code, like property declarations, from view methods.

Xcode also supports TODO: and FIXME landmarks to annotate your code in the jump bar.

class SomeViewController: UIViewController, UITableViewDatasource, UITableViewDelegate {
  //MARK: - Properties
  @IBOutlet weak var tableView: UITableView!
  let cells = ["first cell", "second cell"]


  //MARK: - View Lifecycle
  override func viewDidLoad() {
    super.viewDidLoad()
  }

  override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    //TODO: Finish this method
  }


  //MARK: Table View Datasource
  override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
  }

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return cells.count
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
    //FIXME
    cell.textLabel?.text = cell[indexPath.row]

    return cell
  }


  //MARK: Table View Delegate
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    print("selected cell at index: \(indexPath.row)")
  }
}

Spacing

  • Indent using tabs, not spaces, with a width of 4 spaces
  • Never use spaces between parentheses and their contents, but always leave one space before the opening parenthesis for a if/else/for/switch/while condition
  • Parentheses are not required for if/else/for/switch/while conditions. Only add them for complicated conditions when it adds clarity. Long conditions can also be split on to multiple lines for clarity.
  • Method braces and other braces if/else/for/switch/while etc. always open on the same line as the statement, and close on a new line
  • There should be exactly one blank line between methods to aid in visual clarity and organization. There should be exactly two blank lines before a //MARK: or a /** */ or /// documentation comment
  • Whitespace within methods should separate functionality, but having too many sections in a method often means you should refactor into several methods.
if someMethod.isTrue {

}

if ((a + b + c) < (d + e + f)) {

}

for i in 0..<maxValue {

}

if let a = a,
       b = b,
       c = c,
       where c != 0 {

}


//MARK: Functions (2 spaces)
func myFunction() {

}

func anotherFunction() { //(1 space)
	//The label initialization functions are grouped together
	let label = UILabel() 
	label.frame = CGRect(x: 0, y: 0, width: 100, height: 30)
	label.text = "Text"

	//The process of adding the label to the view is seperated by a blank line
	let view = UIView
	view.addSubview(label)
}


///Documented Function (2 spaces)
func documentedFunction() {

}

Xcode Project

  • The directory structure should be kept in sync with the file groups Xcode
  • Files within groups should be kept alphabetized
  • Multiple similar files may be grouped together in Xcode and on the file system in a directory
  • Project Structure
    • Base folder (Contains git files and other non-Xcode configuration files as necessary)
      • Pods/ (if using CocoaPods)
      • ProjectName/
      • ProjectNameTests/
      • ProjectName.xcodeproj/
      • ProjectName.xcodeworkspace/ (if using CocoaPods)
  • There should be no files directory within an Xcode ProjectName directory, or not inside a group in the Xcode project structure. The subfolders and corresponding groups should follow this structure. All class names should match their corresponding file names.
    • ProjectName/
      • Models/ (Contains all model classes)
        • ProjectName.xcdatamodeld (if using Core Data)
        • RLMSupport.swift (if using Realm)
      • Views/ (Contains .xib's and UI subclasses within a folder structure that mirrors the app navigation)
      • Storyboards/ (Contains .storyboard's. For larger projects, the interface should be split up into multiple storyboards, eg. one for each tab on a tab bar. This will help organize the storyboard, and improve compile time.)
      • Controllers/ (Contains view controllers within a folder structure that mirrors the app navigation)
      • Base.lproj/ (if using localized strings)
      • Utilities/ (Contains utility classes and singletons)
      • Resources/
        • Fonts/
        • Images/ (Contains .xcassets to group images for similar purposes. The bundle should be named like Home.xcassets and all internal images should be prefixed with Home_ and should be in camel case with an initial capital, eg. Home_Background. Assets should be split up into multiple bundles to improve compile time, and ease in using assets.
        • Strings/ (Contains .plist's for localized strings
      • Supporting Files/ (AppDelegate, InfoPlist, ProjectName-Info.plist, ProjectName-Prefix.pch, Bridging-headers, etc
      • Frameworks/ (Any added frameworks and 3rd party classes added to the project including all git submodules)

Versioning:

  • Version numbers should follow the standard SemVer format. The version number should be bumped whenever a new build is pushed to the app store.
  • Build numbers should reset to 0 when the version is bumped. The build number should be bumped for each successive build that is uploaded to iTunes Connect.

Tests

  • Test class file structure should match the class file structure
  • All test classes should have the same name as the corresponding class, with Tests appended. eg: UtilsTests.swift
  • The class and file names should be the same
  • All test classes should import XCTest and inherit from XCTestCase
  • All test cases should start with test, and the rest of the name should describe the method that is being tested, and what is being tested in camel case, eg. testValidateBlankUsername
  • Mock classes can be declared inline in the test case. This is a huge improvement over Objective-C where external mocking libraries were often used to ease the creation of fake data.
  • For each test assertion, describe what went wrong if the assertion were to fail
import XCTest

class SomeClassTests: XCTestCase {
	func testButtonPressed() {
		class MockData: DataObject {
			func overriddenFunc() {
				XCTAssert(1 == 1, "1 does not equal 1")
			}
		}
	}
}