Exploring Freelancing
Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!
- What are Swift extensions?
- Creating an extension in Swift
- Type properties
- Mutating methods
- Separating code
- Extension on SwiftUI View
- Adding initializers to existing types
- Conclusion
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!
Exploring Freelancing
Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!