Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!

This post takes you through a basic overview of extensions in Swift. It demonstrates how Swift extensions work by using examples from the code of Ellifit, a simple workout-tracking app.

Originally written for LogRocket, modified to use it in my blog.

What are Swift extensions?

Extensions, well, extend existing Swift named types — i.e., structs, classes, enums, and protocol — so you can add more functionality to them.

This enables you to insert your code into existing system code to which you wouldn’t otherwise have access, such as the Foundation framework. You can also use extensions to extend your code and for code cleanliness.

Creating an extension in Swift

Creating extensions is similar to creating named types in Swift. When creating an extension, you add the word extension before the name of the type you’re extending.

extension SomeNamedType {
  // Extending SomeNamedType, and adding new functionality to it.
}

Type properties

You can extend a particular named type, add a new computed instance, and type properties. For example, you can extend Color to add your colors to it.

The app has a brand color that I want to use everywhere. We can create a constant type property, brand, by extending Color via extension.

The app also uses custom colors for the row’s background in the settings screen. We define a variable type property that adjusts the color according to the system-wise appearance.

extension Color {
  static let brand = Color(red: 75/255, green: 0, blue: 130/255)

  static var settingsBackground: Color {
    Color(UIColor { (trait) -> UIColor in
      return trait.userInterfaceStyle == .dark ? .systemGray5 : .systemGray6
    })
  }
}

Here’s how you can use it in your SwiftUI view:

struct SettingsRow: View {
  var title: String

  var body: some View {
    HStack(spacing: 8) {
      Text(title)
        .foregroundColor(.brand)

      Spacer()

      Image(systemName: "chevron.right")
    }
    .foregroundColor(.settingsBackground)
  }
}

Mutating methods

You can extend types to add your own functionality even though you don’t have access to the original codebase. If you want to add a function to Double, you can write an extension on the struct without having access to the original code of the Double struct.

In the app, I’m fetching calories data from HealthKit, but the function returns the data in Double type. We want to show the data rounded to one decimal place. So, we can write an extension on Double like this:

extension Double {
    mutating func roundTo(places: Int) {
        let divisor = pow(10.0, Double(places))
        self = (self * divisor).rounded() / divisor
    }
}

Let’s use this mutating method to solve the problem:

var caloriesBurned: Double? = 213.3244

if var calories = caloriesBurned {
    calories.roundTo(places: 1)
    print("\(calories) kcal") /// Prints "213.3 kcal"
}

Separating code

When conforming the classes to protocols, you may add all the protocol methods in the same class. For example, we add all the methods of UICollectionViewDataSource, UICollectionViewDelegate, and UICollectionViewDelegateFlowLayout in the same file.

We can separate the methods required for each using extensions. This makes the code more readable and maintainable.

class ExampleViewController: UIViewController {
    // Add the main code goes here
}

// MARK:- UICollectionViewDataSource
extension ExampleViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        //
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        //
    }
}

// MARK:- UICollectionViewDelegate
extension ExampleViewController: UICollectionViewDelegate {
    //
}

// MARK:- UICollectionViewDelegateFlowLayout
extension ExampleViewController: UICollectionViewDelegateFlowLayout {
    //
}

This is an old way of using Google Sign-In. You don’t need the delegate anymore.

The app uses Google Sign-In as the main authentication source, so we need to conform to GIDSignInDelegate to receive updates on successful sign-in. We can separate the code required for this — you guessed it — using extensions.

import GoogleSignIn

class AuthenticationViewModel: NSObject, ObservableObject {
  /// Main code goes here
}

// MARK:- GIDSignInDelegate
extension AuthenticationViewModel: GIDSignInDelegate {
    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
        if error == nil {
            // Authentication successful
        } else {
            print(error.debugDescription)
        }
    }
}

Extension on SwiftUI View

I want to add a custom large title text, similar to what Apple uses for the header in most of its apps. But, I don’t want the default one that you get with navigationTitle(), as when you scroll, it becomes an inline title.

The title text denotes the date of a particular workout, and I want the exact custom text for the settings screen.

To reuse this code everywhere in the codebase, we extend Text by adding a largeTitle() method:

extension Text {
    func largeTitle() -> some View {
        self
            .bold()
            .foregroundColor(.primary)
            .font(.largeTitle)
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.top, 37)
    }
}

Now we can use this method on the views:

VStack {
    Text("Settings").largeTitle()
}

Similarly, to create a heart button to favorite a set of workouts, we create a ViewModifier that toggles the color of the heart on double-tap:

struct HeartButtonModifier: ViewModifier {
    @Binding var state: Bool

    func body(content: Content) -> some View {
        content
            .foregroundColor(state ? .red : .secondary)
            .onTapGesture(count: 2) {
                state.toggle()
            }
    }
}

Now let’s create an extension on View so we can use it in our views:

extension View {
    func workoutLiked(state: Binding<Bool>) -> some View {
        self.modifier(HeartButtonModifier(state: state))
    }
}

Adding initializers to existing types

We can use an extension to add a new custom initializer that accepts different parameters to existing types.

Let’s assume that your designer gives you the colors in hex instead of the RGB value. Using the previous examples of adding a computed type property to Color, we’ll create an initializer that takes a hex value. We can add another initializer if we want to make a color with the RGB value as integers:

extension Color {
    init(hex: Int) {
        let red = (hex >> 16) & 0xFF
        let green = (hex >> 8) & 0xFF
        let blue = hex & 0xFF

        self.init(red: red, green: green, blue: blue)
    }

    init(red: Int, green: Int, blue: Int) {
        let red = Double(red) / 255
        let green = Double(green) / 255
        let blue = Double(blue) / 255

        self.init(red: red, green: green, blue: blue, opacity: 1.0)
    }
}

We can now use it as:

extension Color {
    static var brand: Color {
        Color(hex: 0x4B0082)
    }

    static var secondaryBrand: Color {
        Color(red: 41, green: 0, blue: 71)
    }
}

Conclusion

Extensions in Swift are a powerful way to add your functionality to types that you do not own. This overview of extensions and the examples herein are designed to help you understand how extensions work so you can implement and use them in your own Swift projects.

For further advanced reading, I recommend the following articles from the Swift docs:

I hope you enjoyed reading this post! If you’ve any other ways to implement extensions or any other feedback/constructive criticism, please let me know on Twitter!

Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!