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:
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 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. }
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) } }
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:
var caloriesBurned: Double? = 213.3244 if var calories = caloriesBurned { calories.roundTo(places: 1) print("\(calories) kcal") /// Prints "213.3 kcal" }
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) } } }
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) } }
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) } }
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:
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.