Swift Tips

Apart from my main weekly article series, my podcast, and my Basics articles — I also love to share brief Swift tips and tricks with the community on Twitter.

This page contains a collection of some of those tips. More tips and tricks will be added very soon, and there are also links to related content on the same topic beneath each tip.

To subscribe to these Swift tips, either follow me on Twitter, or sign up for the monthly Swift by Sundell newsletter.


Default enum associated values in Swift 5.1

New in Swift 5.1: Enum cases can now specify defaults for their associated values — which comes very much in handy when we want to add options or overrides to a given enum case.

// Associated enum value defaults are specified the same way as
// default function arguments:
enum Content {
    case text(String)
    case image(Image, description: String? = nil)
    case video(Video, autoplay: Bool = false)
}

// At the call site, any associated value that has a default
// can be omitted, and the default will be used:
let video = Content.video(Video(url: url))

The power of variadic parameters

I’m a big fan of functions with variadic parameters in Swift, especially when I want an API to feel a bit more “lightweight”, since no array needs to be created at the call site in order to pass multiple (or zero) arguments.

// When using a variadic parameter, any number of arguments can
// be passed, and the compiler will automatically organize them
// into an array.
func send(_ message: Message, attaching attachments: Attachment...) {
    ...
}

// Passing no variadic arguments:
send(message)

// Passing either a single, or multiple variadic arguments:
send(message, attaching: image)
send(message, attaching: image, document)

Configurable types

A big benefit of making types configurable by closures, rather than hard-coding their behaviors, is that it makes it trivial to add new capabilities. For example, here’s how I'm adding a Splash plugin to my upcoming static site generator using a configurable type:

extension PipelineBuilder {
    mutating func splash() {
        customStep { context in
            context.siteMap.transformItems { item in
                item.body = MarkdownDecorator().decorate(item.body)
            }
        }
    }
}

Related articles:


Improved compiler errors for auto-synthesized conformances in Xcode 11

New in Xcode 11/Swift 5.1: The compiler will now tell you why it can't auto-synthesize conformance to protocols like Encodable and Decodable, instead of just suggesting a manual implementation. Let’s say we have the following type, that we wish to conform to Decodable automatically:

struct ItemMetadata: Decodable {
    var date: Date
    var format: ItemFormat
    var category: String
}

If the ItemFormat type used above isn’t Decodable, then the compiler will now give us the following error:

Cannot automatically synthesize 'Decodable' because 'ItemFormat'
does not conform to Decodable.

Improved memberwise initializers in Swift 5.1

New in Swift 5.1: When a struct property has a default value, it’s no longer required to pass a value for it when using the compiler-generated initializer. One of my favorite tweaks in this version of Swift — it’s a small feature, but makes structs feel even more lightweight.

// In Swift 5.1, when a struct property has a default value,
// it's no longer required to pass a value for it when calling
// the compiler-generated initializer.
struct Article {
    var title: String
    var tags = [Tag]()
}

// Meaning that we can now create an Article value without
// supplying any tags:
Article(title: "Shifting paradigms in Swift")

// We can of course still pass values for all properties when
// we want to:
Article(title: "Access control", tags: [Tag(name: "Basics")])

Using SwiftUI without the macOS beta

You can totally start learning and experimenting with SwiftUI on iOS without running the macOS 10.15 beta. Just fire up a playground, import PlaygroundSupport, and assign a UIHostingController as your live view. It’s just the new Canvas feature that only works on 10.15.

import SwiftUI
import PlaygroundSupport

struct MyView: View {
    var body: some View {
        Text("Hello, world!")
    }
}

let vc = UIHostingController(rootView: MyView())
PlaygroundPage.current.liveView = vc

Related articles:


Refactoring SwiftUI views using functions

I see lots of SwiftUI comments about how it’ll “force” developers to write “Pyramids of Doom” with heavily nested code, which is just as false as MVC forcing developers to build massive view controllers. Here’s one example of how nesting can be avoided, by using inline functions:

struct MyView: View {
    var body: some View {
        func makeVStack() -> some View {
            VStack {
                ForEach(0..<5) { _ in makeHStack() }
            }
        }

        func makeHStack() -> some View {
            HStack {
                Text("Leading")
                Text("Trailing")
            }
        }

        return ZStack {
            Color.gray
            makeVStack()
        }
    }
}

Related articles:


Stacking views in SwiftUI

SwiftUI ships with 3 main stack types — that enable you to easily align content on either the X, Y, or Z axis. And, since all SwiftUI views are composable, they can also be nested in order to build really complex UIs:

struct MyView: View {
    var body: some View {
        // A ZStack stacks its child views on top of each other
        // in terms of depth, from back to front.
        ZStack {
            Color.gray
            // A VStack stacks its child views vertically, from
            // top to bottom.
            VStack {
                // The ForEach construct enables you to create
                // multiple views in one go, for example by
                // mapping a collection of elements to views.
                ForEach(0..<5) { _ in
                    // A HStack aligns its child views horizontally,
                    // from the leading to the trailing edge.
                    HStack {
                        Text("Leading")
                        Text("Trailing")
                    }
                }
            }
        }
    }
}

The above will result in a view which content looks like this:

Leading Trailing
Leading Trailing
Leading Trailing
Leading Trailing
Leading Trailing

SwiftUI mix and match

You can definitely mix and match SwiftUI with UIKit/AppKit, which means that you can adopt it gradually. Any SwiftUI hierarchy can be embedded in a view controller, and UIKit views can retrofitted with SwiftUI support:

// You can easily use SwiftUI view hierarchies in UIKit-based
// UIs, by wrapping them in a UIHostingController:
let vc = UIHostingController(rootView: HeaderView())

// For the opposite direction, using UIViews with SwiftUI — just
// create a wrapper that conforms to UIViewRepresentable, which
// acts just like any other SwiftUI view:
struct ProfileSwiftUIView: UIViewRepresentable {
    let user: User

    func makeUIView(context: Context) -> ProfileView {
        return ProfileView()
    }

    func updateUIView(_ view: ProfileView, context: Context) {
        view.nameLabel.text = user.name
        view.imageView.image = user.image
    }
}

Related articles:


SwiftUI is a game changer

There’s no doubt in my mind that SwiftUI is a complete game changer. This is all the code you need to define a UI that has:

  • A navigation controller
  • A table view
  • Cell configuration
  • Label font setup
struct ArticleListView: View {
    let articles: [Article]

    var body: some View {
        NavigationView {
            List(articles.identified(by: \.id)) { article in
                VStack(alignment: .leading) {
                    Text(article.title).font(.headline)
                    Text(article.preview).font(.subheadline)
                }
            }.navigationBarTitle(Text("Articles"))
        }
    }
}

Related content:


Functional networking

Instead of complex network managers, or view controllers containing hundreds of lines of networking code, I always try to reduce my networking into functions that I can pass into my view controllers:

// With Futures/Promises:
typealias Loading<T> = () -> Future<T>

// Without Futures/Promises:
typealias Loading<T> = (@escaping (T) -> Void) -> Void

// If we model our networking code using functions, our view
// controllers no longer need to be aware of any networking,
// and can simply call those functions in order to retrieve
// their data:
class ProductViewController: UIViewController {
    private let loading: Loading<Product>

    init(loading: @escaping Loading<Product>) {
        self.loading = loading
        super.init(nibName: nil, bundle: nil)
    }

    func loadProduct() {
        loading().observe {
            ...
        }
    }
}

Related articles:


Testing custom Codable implementations

Here's a very simple way to test custom Codable implementations — by just encoding and then decoding a value, and then verifying that the initial value and the decoded one are equal. Doesn't test edge cases, but it's a good start:

class UserTests: XCTestCase {
    func testCoding() throws {
        // Creating an instance of the Codable type that
        // we want to test:
        let user = User(
            id: 7,
            name: "John",
            notes: [
                Note(string: "Swift is awesome!")
            ]
        )

        // Encoding the instance into Data, then immediately
        // decoding that data back into a new instance:
        let data = try user.encoded()
        let decodedUser = try data.decoded() as User

        // Asserting that the two instances are equal:
        XCTAssertEqual(user, decodedUser)
    }
}

Related articles:


Refactoring enums which cases contain the same data

Enums with associated values are awesome, but if many cases need to include the same data, it’s perhaps time to refactor that enum into a different solution — for example by wrapping it in a struct that contains the shared data.

Here all of our Warning enum’s cases contains a path, so to reduce duplication and making handling paths easier, we move it to a wrapping struct instead:

// Before

enum Warning {
    case fileUnreadable(path: String)
    case decodingFailed(path: String, DecodingError)
    case unknownError(path: String, Error)
}

// After

struct Warning {
    let path: String
    let reason: Reason
}

extension Warning {
    enum Reason {
        case fileUnreadable
        case decodingFailed(DecodingError)
        case unknownError(Error)
    }
}

Related articles:


Converting between String and Data without optionals

While Swift offers many APIs for converting between Data and String, these two let you do so without any optionals — usually resulting in simpler code:

// String -> Data
let data = Data("Hello, world!".utf8)

// Data -> String
let string = String(decoding: data, as: UTF8.self)

Related content:


Swift packages containing both a command line tool and a library

If we want to distribute a Swift package as both a library that other tools can import, and as a command line tool, all we have to do is to define two separate targets and products.

Here’s an example Package.swift file in which we’re doing exactly that:

// swift-tools-version:5.0

import PackageDescription

let package = Package(
    name: "Plot",
    products: [
        // If we want to distribute a Swift package as both a
        // library and a command line tool, one way to do that
        // is to simply use lower case characters for the CLI.
        .library(name: "Plot", targets: ["Plot"]),
        .executable(name: "plot", targets: ["PlotCLI"])
    ],
    dependencies: [
        .package(url: "https://github.com/johnsundell/files.git", from: "3.1.0"),
        .package(url: "https://github.com/johnsundell/sweep.git", from: "0.2.1"),
        .package(url: "https://github.com/johnsundell/codextended.git", from: "0.2.0")
    ],
    targets: [
        .target(
            name: "Plot",
            dependencies: ["Files", "Sweep", "Codextended"]
        ),
        .target(
            // We can still name our target "CLI", to make it
            // easier to see the difference between our CLI
            // and our library in Xcode.
            name: "PlotCLI",
            dependencies: ["Plot"]
        ),
        .testTarget(
            name: "PlotTests",
            dependencies: ["Plot", "Codextended"]
        )
    ]
)

Related content:

3 different ways of mutating all values in a dictionary

One of my favorite things about programming is that there’s very rarely a “one true way” to do things — we often have multiple potential solutions at our disposal, letting us pick whichever one is the most appropriate for each situation.

Here’s an example of three different ways of mutating all values within a dictionary:

// Making each (key, value) tuple's value mutable

struct Inbox {
    var emails: [Email.ID : Email]

    mutating func markAllAsRead() {
        // When iterating over a collection of tuples (such as
        // keys & values in a dictionary), we can optionally choose
        // to make a member mutable by prefixing it with 'var'.
        for (id, var email) in emails {
            email.wasRead = true
            emails[id] = email
        }
    }
}

// Using mapValues (added in Swift 4)

struct Inbox {
    var emails: [Email.ID : Email]

    mutating func markAllAsRead() {
        // Another way to achieve the same thing as above would
        // be to use 'mapValues' instead, which lets us mutate
        // each value within a dictionary.
        emails = emails.mapValues { email in
            var email = email
            email.wasRead = true
            return email
        }
    }
}

// Iterating over all of the dictionary's keys

struct Inbox {
    var emails: [Email.ID : Email]

    mutating func markAllAsRead() {
        // And yet another option would be to instead iterate
        // over all of the dictionary's keys and perform an
        // inline mutation.
        for id in emails.keys {
            emails[id]?.wasRead = true
        }
    }
}

Related articles:


Swift’s composition operator

Swift’s & operator is really powerful. Not only does it let us compose multiple protocols, we can also use it to combine concrete types and protocols as well:

protocol Reloadable {
    func reload()
}

// Using the '&' operator, we can not only compose multiple
// protocols — we can also combine a concrete type with a
// protocol to form a brand new type.
typealias ReloadableViewController = UIViewController & Reloadable

// Our above type alias acts like a proper type in most cases,
// we can even inherit directly from it!
class ProfileViewController: ReloadableViewController {
    private lazy var tableView = UITableView()

    func reload() {
        tableView.reloadData()
    }
}

Related articles:


Generic algorithms

A big benefit of Swift’s protocol-oriented design is that it makes it much easier to implement generic algorithms that can be used in many different contexts. For example, here we’re not only extending Array - we’re extending all RandomAccessCollection types:

extension RandomAccessCollection {
    func element(at index: Index) -> Element? {
        guard indices.contains(index) else {
            return nil
        }

        return self[index]
    }
}

By implementing extensions on standard library protocols instead of concrete types, our added functionality can be used in so many more contexts. For example, the above extension can now be used not only on Array, but also on Data, many kinds of ranges, IndexPath, KeyValuePairs, and many more.

Related articles:

Protocols “inheriting” from classes

New in Swift 5 is that protocols can now ”inherit” from classes — requiring all types conforming to that protocol to also inherit from the specified class:

// Here the Component protocol "inherits" from UIView, essentially
// requiring all types conforming to the protocol to also inherit
// from the same class.
protocol Component: UIView {
    associatedtype Model

    func activate(with model: Model)
    func deactivate()
}

// This is especially useful when one of our protocols really
// only makes sense within a certain context — such as UIViews
// in this case.
class ImageComponent: UIImageView, Component {
    func activate(with model: UIImage) {
        image = model
    }

    func deactivate() {
        image = nil
    }
}

This is essentially new syntactic sugar around protocol constraints/specialization, which you can read more about in "Specializing protocols in Swift”, linked below.

Related articles:


The ‘final’ keyword

Swift's final keyword is not only useful in order to prevent a class from being subclassed entirely, it can also let us mark individual methods as non-overridable — which is useful when relying on subclassing, and we still want to protect certain methods from being modified:

// A class that isn't 'final' can be subclassed within its own
// module (outside of the module, only if it's marked as 'open').
class Actor {
    func willMove(to scene: Scene) {
        ...
    }

    func didMove(to scene: Scene) {
        ...
    }

    // However, individual functions can still be marked as
    // 'final' to prevent overrides.
    final func remove() {
        ...
    }
}

extension Actor {
    // Methods defined in extensions can't be overriden either,
    // at least not yet.
    func add(to scene: Scene) {
        ...
    }
}

class Player: Actor {
    // This is especially useful when setting up a hierarchy
    // of classes that derive from a parent class, and we only
    // want to enable specific overrides.
    override func willMove(to scene: Scene) {
        super.willMove(to: scene)
        ...
    }
}

The power of key paths

Swift’s key paths can let us build APIs that are both really powerful, but also look really nice and clean at the call site. Here we have an extension that lets us easily group any Sequence based on an Element key path:

// This extension will let us group any Sequence (such as an
// Array or a Set), based on a given key path.
extension Sequence {
    func grouped<T: Hashable>(by keyPath: KeyPath<Element, T>) -> [T: [Element]] {
        // Using key path subscripting syntax, we can dynamically
        // access a member of a type based on a key path.
        return .init(grouping: self, by: { $0[keyPath: keyPath] })
    }
}

func scan(_ string: String, using matchers: [Matcher]) {
    // The result is that our call sites become really clean, since
    // we can simply use a key path literal to group any sequence.
    let matchersByPattern = (
        start: matchers.grouped(by: \.pattern.start),
        end: matchers.grouped(by: \.pattern.end)
    )

    ...
}

Related articles:


Recursive enums with “fake” cases

Indirect enums can provide a great way to set up recursive data structures with a finite number of cases, and static functions can let us define “fake” cases that look like normal cases, but don’t require additional handling in switches:

// Indirect enums are able to reference themselves, enabling
// us to set up recursive data structures.
indirect enum Content {
    case values(Values)
    case script(Script)
    case collection([Content])
}

extension Content {
    // Using static functions, we can create "fake" enum cases,
    // that can act as convenience APIs — without having to add
    // additional case handling code in our switch statements.
    static func text(_ text: String) -> Content {
        return .values(["content": text])
    }
}