Swift: Approaches to Constants Files
It is common for app projects to have a separate file for all constants. Here you would define things like table view cell identifiers, segue identifiers, common number values, common literal strings, or any other values that get reused around your codebase.
Flat
One simple approach to this in Swift is to have one flat file for all of these constant declarations. Let's call our file Constants.swift
:
//
// Constants.swift
//
import Foundation
let LargeCellIdentifier = "LargeCellIdentifier"
let DefaultFontSize: CGFloat = 14.0
let BlueCellIdentifier = "BlueCellIdentifier"
let SegueToDetailViewController = "SegueToDetailViewController"
let DetailViewTableViewControllerCellIdentifier = "DetailViewTableViewControllerCellIdentifier"
let MasterViewControllerCellIdentifier = "MasterViewControllerCellIdentifier"
let SegueToMasterViewController = "SegueToMasterViewController"
// etc.
This approach may only be suitable for small projects. The problem is this file can get messy and complex as the size of the project grows. Soon you may have thousands of these constants packed into one long list. As time goes by, it can become difficult to find constants, and also difficult to decide where to place new constants in the already bloated file.
// MARK: -
A quick fix for this would be to use // MARK: -
as a way to group our constants by category:
//
// Constants.swift
//
import Foundation
// MARK: - TableView Cell Identifiers
let BlueCellIdentifier = "BlueCellIdentifier"
let LargeCellIdentifier = "LargeCellIdentifier"
let DetailViewTableViewControllerCellIdentifier = "DetailViewTableViewControllerCellIdentifier"
let MasterViewControllerCellIdentifier = "MasterViewControllerCellIdentifier"
// MARK: - Font Sizes
let DefaultFontSize: CGFloat = 14.0
// MARK: - Segue Identifiers
let SegueToMasterViewController = "SegueToMasterViewController"
let SegueToDetailViewController = "SegueToDetailViewController"
// MARK: - API Paths
// etc.
Another nice benefit of this is pressing ctrl+6
in Xcode will open the jump bar and we can then select a desired group which will then scroll to it in our constants file.
Enums
Another approach is to use Swift enums:
//
// Constants.swift
//
import Foundation
enum CellIdentifiers: String {
case Blue = "BlueCellIdentifier"
case Large = "LargeCellIdentifier"
}
enum FontSizes: CGFloat {
case Large = 14.0
case Small = 10.0
}
enum AnimationTimes: NSTimeInterval {
case Fast = 1.0
case Medium = 3.0
case Slow = 5.0
}
enum Emojis: Character {
case Happy = "😄"
case Sad = "😢"
}
enum SegueIdentifiers: String {
case Master = "MasterViewController"
case Detail = "DetailViewController"
}
Here we need to use enums with raw values. In our example, the CellIdentifiers
enum has a String
raw value type. This approach seems cleaner from a grouping standpoint compared to the flat approach. To use this in our code we need to access the rawValue
property:
let segueIdentifier = SegueIdentifiers.Master.rawValue
This is a nice improvement in terms of organization and readability of our code. I personally find the dot syntax in SegueIdentifiers.Master.rawValue
easier to read and more logical compared to something like the camel-cased SegueToMasterViewController
.
One potential drawback of the enum approach is limited choice of types. The docs say that enum raw values can only be strings, characters, or any of the integer or floating-point number types. Another minor issue is that you need to add the trailing rawValue
every time you need to access the value of the constant.
Structs
Another approach is to use Swift structures with constant type properties:
struct CellIdentifiers {
static let Blue = "BlueCellIdentifier"
static let Large = "LargeCellIdentifier"
}
struct FontSizes {
static let Large: CGFloat = 14.0
static let Small: CGFloat = 10.0
}
struct AnimationDurations {
static let Fast: NSTimeInterval = 1.0
static let Medium: NSTimeInterval = 3.0
static let Slow: NSTimeInterval = 5.0
}
struct Emojis {
static let Happy = "😄"
static let Sad = "😢"
}
struct SegueIdentifiers {
static let Master = "MasterViewController"
static let Detail = "DetailViewController"
}
Saying static let...
here means that it is a constant property of the type itself rather than on instances of that type. We can easily use this in our code using dot syntax:
UIView.animateWithDuration(AnimationDurations.Slow) {
aCustomView.alpha = 0.0
}
This is nicer from a caller's perspective compared to the enum approach without the need for using rawValue
each time we want to use the constant. Compare: AnimationDurations.Slow
vs. AnimationDurations.Slow.rawValue
. We also have the flexibility of using any type we choose. One drawback is that type inference may not work for all situations. We may need to specify the type with every constant declaration to satisfy the compiler. In our example, animateWithDuration
takes an NSTimeInterval
and will trigger a build error if passed in a Float
. With an enum, we only need to specify the rawValue
type once.
Nested Structs
Taking this further, we could use these structs as nested structs inside one large Constants
struct:
struct Constants {
struct CellIdentifiers {
static let Blue = "BlueCellIdentifier"
static let Large = "LargeCellIdentifier"
}
struct FontSizes {
static let Large: CGFloat = 14.0
static let Small: CGFloat = 10.0
}
struct AnimationDurations {
static let Fast: NSTimeInterval = 1.0
static let Medium: NSTimeInterval = 3.0
static let Slow: NSTimeInterval = 5.0
}
struct Emojis {
static let Happy = "😄"
static let Sad = "😢"
}
struct SegueIdentifiers {
static let Master = "MasterViewController"
static let Detail = "DetailViewController"
}
}
This makes our code more explicit from a calling perspective; we know we are dealing with our constants rather than just some other type. And we can use it with the same dot syntax, but with the leading Constants
:
let happyEmoji = Constants.Emojis.Happy
println("Swift is fun! \(happyEmoji)") // Swift is fun! 😄