In this tutorial, we’ll walk you through the basics of extending in Swift. We’ll demonstrate how the Swift extension works by building a simple exercise tracking application.
We will highlight the following.
- What is Swift Extension?
- Create an extension in Swift
- The type attribute
- Variation method
- The separation of code
- Expand on SwiftUI view
- Adds an initializer for an existing type
What is Swift Extension?
Extensions, well, extend existing Swift-named types — that is, structures, classes, enumerations, and protocols — so you can add more functionality to them. This enables you to insert your own code into existing system code that you would otherwise not be able to access, such as the Foundation framework. You can also use extensions to extend and clean your own code.
Create an extension in Swift
Creating an extension is similar to creating a named type in Swift. When you create an extension, you add extension before the name.
extension SomeNamedType {
// Extending SomeNamedType, and adding new
// functionality to it.
}
Copy the code
The type attribute
You can extend a particular named type, add a new instance, and assign type attributes to it. For example, you can extend Color and add your own colors to it. Let’s say our app has a brand color that we want to use everywhere. We can extend Color with extension to create a constant type attribute, brand.
Our application also uses custom colors for row backgrounds in the Settings screen. To do this, we will define a variable type property that adjusts the color based on the appearance of the system.
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
})
}
}
Copy the code
Here’s how to 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)
}
}
Copy the code
Variation method
As mentioned in the introduction, you can extend types to add your own functionality, even if you don’t have access to the original code base. For example, if you want to give a Double, you can write an extension on the structure without having to access the original code of the Double structure.
In our application, we are getting calorie data from HealthKit, but the function returns data of type Double. We want to display data rounded to one decimal point. We can write an extension in Double like this.
extension Double { mutating func roundTo(places: Int) {let divisor = pow(10.0, Double(places)) self = (self * divisor).rounded()/divisor}}Copy the code
Let’s use this mutation method to solve our problem.
var caloriesBurned: Double? = 213.3244 if var calorie = caloriesBurned {calorie. RoundTo (places: 1) print("\(calories) kcal") // Prints "213.3kcal"}Copy the code
The separation of code
When our class is protocol compliant, we usually add all the protocol methods in the same class. For example, we will add UICollectionViewDataSource, UICollectionViewDelegate and UICollectionViewDelegateFlowLayout all methods.
We can use extensions to isolate each of the required methods. This makes the code more readable and easier to maintain.
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 {
//
}
Copy the code
Our application uses Google Sign-in as the primary authentication source, so we need to conform to GIDSignInDelegate to receive updates after a successful login. We can separate the code needed to do this — you guessed it — with 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) } } }Copy the code
Extension on SwiftUI View
Now, let’s say we want to add a custom headline text, like the one Apple uses for headlines in most of its applications. This text will indicate the date of a workout. We also want to use the exact custom text on the Settings screen.
To reuse this code in the code base, we’ll extend Text to add a largeTitle(:) method.
extension Text {
func largeTitle() -> some View {
self
.bold()
.foregroundColor(.primary)
.font(.largeTitle)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 37)
}
}
Copy the code
Now we can use this method on our view.
VStack {
Text("Settings").largeTitle()
}
Copy the code
Similarly, suppose we want to create a heart-shaped button for a collection of exercises. We will create a ViewModifier that switches the color of the heart on a double click.
struct HeartButtonModifier: ViewModifier {
@Binding var state: Bool
func body(content: Content) -> some View {
content
.foregroundColor(state ? .red : .secondary)
.onTapGesture(count: 2) {
state.toggle()
}
}
}
Copy the code
Now let’s create an extension on the View so that we can use it in our View.
extension View {
func workoutLiked(state: Binding<Bool>) -> some View {
self.modifier(HeartButtonModifier(state: state))
}
}
Copy the code
Finally, we add it to the Image as a modifier.
struct LikeView: View {
@State private var state = false
var body: some View {
Image(systemName: "heart.fill")
.workoutLiked(state: $state)
}
}
Copy the code
Adds an initializer for an existing type
We can use an extension to add a new custom initializer to an existing type that accepts different parameters.
Let’s assume that your designer gives you a hexadecimal color instead of an RGB value. Using the previous example of adding a calculated type attribute to Color, we will create an initializer that accepts hexadecimal values. If we want RGB values to be integer colors, we can add another initializer.
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: Opacity: 1.0)}}Copy the code
We can now use it as a.
extension Color {
static var brand: Color {
Color(hex: 0x4B0082)
}
static var secondaryBrand: Color {
Color(red: 41, green: 0, blue: 71)
}
}
Copy the code
conclusion
Extensions in Swift are a powerful way to add your own functionality to types that don’t belong to you. This overview of extensions and the examples here are intended to help you understand how extensions work so that you can implement and use them in your own Swift projects.
For further reading, I recommend the following article in the Swift documentation.
- “Add protocol consistency with extensions”
- “Extensions using generic Where statements”
The postSwift Extensions: An overview with examples appears on The LogRocket blog.