Rudrank Riyam Apple Platforms developer. WWDC '19 scholar.

Swift extensions: An overview with examples

3 min read 1078

Swift Logo Over a Blue Background

In this tutorial, we’ll take you through a basic overview of extensions in Swift. We’ll demonstrate how Swift extensions work by building a simple workout tracking app.

We’ll focus on the following:

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 own 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 own 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.

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 to it. For example, you can extend Color to add your own colors to it. Let’s say our app has a brand color that we want to use everywhere. We could create a constant type property, brand, by extending Color via extension.

Our app also uses custom colors for the row’s background in the settings screen. For this, we’ll 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 use it:

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

As mentioned in the introduction, 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, for example, you can write an extension on the struct without having access to the original code of Double struct.

In our app, we’re 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. 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 our problem:

We made a custom demo for .
No really. Click here to check it out.

var caloriesBurned: Double? = 213.3244

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

Separating code

When conforming our classes to protocols, we usually add all the protocol methods in the same class. For example, we would add all the methods of UICollectionViewDataSource, UICollectionViewDelegate, and UICollectionViewDelegateFlowLayout.

We can separate the methods required for each using extensions. This makes the code more readable and more 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 {
    //
}

Our 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 views

Now let’s say we want to add a custom large title text like Apple uses for the header in most of its apps. This text will denote the date of a particular workout. We want to use the exact custom text for the settings screen as well.

To reuse this piece of code everywhere in the codebase, we will 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 our views:

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

Similarly, let’s say we want to create a heart button to favorite a set of workouts. We’ll 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))
    }
}

Finally, we’ll add it as a modifier to the Image:

struct LikeView: View {
    @State private var state = false

    var body: some View {
        Image(systemName: "heart.fill")
            .workoutLiked(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 own 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:

 

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Rudrank Riyam Apple Platforms developer. WWDC '19 scholar.

Leave a Reply