SwiftUI View Modifiers Tutorial for iOS
Learn how to refactor your code to create powerful custom SwiftUI view modifiers. Make your views look consistent and your code easier to read and maintain. By Danijela Vrzan.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
SwiftUI View Modifiers Tutorial for iOS
20 mins
- Getting Started
- View Modifiers in SwiftUI
- Building a Title ViewModifier
- Creating a Text ViewModifier
- Extending the Text View
- Building a Button Style
- Creating a Button ViewModifier
- Refactoring ViewModifier Into a ButtonStyle
- Using the isPressed Property on Button Style
- Conditional ViewModifiers
- Creating a Conditional ViewModifier
- View Extensions With Default Parameters
- Creating an Image Modifier
- Ready for a Challenge?
- Where to Go From Here?
Extending the Text View
Open ViewModifiers.swift and replace // TODO: 2
with:
extension Text {
func detailedInfoTitle() -> some View {
modifier(DetailedInfoTitleModifier())
}
}
Here, you create a Text
extension with a detailedInfoTitle()
method that returns
some View
. Inside, you add one line of code that calls the modifier you’ve created in a previous step.
The name of the method is the name of your custom modifier. Now you don’t have to call .modifier()
every time you apply it.
Open PetDetailedInformationView.swift and replace .modifier(DetailedInfoTitleModifier())
with a call to this new method:
.detailedInfoTitle()
Replace it under all five titles.
Build and run. Again, there’ll be no visible changes to your UI:
Congratulations! You’ve built your first view modifier.
Why did you make me go through all this to move my code to another file, you ask?
Well, the code you have in front of you is a simple app with a simple UI. When you created the modifier and moved a few lines of code into a separate file, you made your view smaller and more readable. This becomes noticeable on larger projects that have thousands of lines of code.
But you’re not done yet!
Next, you’ll learn how to create a button modifier with custom styling that changes depending on its isPressed
property.
Building a Button Style
The most common use case for creating custom view modifiers is reusing the same code in multiple places. Here, you’ll do just that by creating a button and giving it a unique style that you’ll reuse across the app.
Open AdoptionFormView.swift. You’ll see a button with Text("Adopt Me")
and a set of styling modifiers on the button’s label:
Now it’s time to learn how to create a custom button style modifier and apply it to the button component.
Creating a Button ViewModifier
Open ViewModifiers.swift and replace // TODO: 3
with:
struct ButtonLabelModifier: ViewModifier {
func body(content: Content) -> some View {
content
.font(.title2)
.padding(.horizontal, 30)
.padding(.vertical, 8)
.foregroundColor(Color.pink)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.pink, lineWidth: 1.5)
)
}
}
Here, you create a ButtonLabelModifier
struct and add the styling modifiers.
Next, in the same file, add the following line at the end of extension Text
:
func buttonLabel() -> some View {
modifier(ButtonLabelModifier())
}
Here, you create a buttonLabel()
method and add the modifier you created above.
Open AdoptionFormView.swift and replace all the modifiers of Text("Adopt Me")
below it with:
.buttonLabel()
Build and run. Your UI looks exactly as it was:
While this modifier works well, in some cases, you don’t want to apply the styling only to your button’s label but to the entire button.
Next, you’ll refactor your button’s label modifier and create a custom button style. This also allows you to apply custom styling depending on the button’s isPressed
property.
Refactoring ViewModifier Into a ButtonStyle
Open ViewModifiers.swift and replace // TODO: 4
with:
// 1
struct PrimaryButtonStyle: ButtonStyle {
// 2
func makeBody(configuration: Configuration) -> some View {
// 3
configuration.label
.font(.title2)
.padding(.horizontal, 30)
.padding(.vertical, 8)
.foregroundColor(Color.pink)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.pink, lineWidth: 1.5)
)
}
}
Here’s a breakdown:
- You create a
PrimaryButtonStyle
struct that conforms toButtonStyle
, the type that provides standard behavior and appearance to all buttons in the view hierarchy. - Then, you add a
makeBody(configuration:)
method that returnssome View
. Theconfiguration
method parameter is of typeConfiguration
. This is just aButtonStyleConfiguration
structtypealias
that’s built into SwiftUI. It has a few properties you can use to customize the button style. - One of those properties is the button’s label. By calling
configuration.label
, you’re explicitly saying you want the modifiers to style the button’s label.
Now you need to apply this button style. When you created the last modifier, you applied it to the button’s label. Now that you created a button style modifier, you need to apply it directly to the button.
Open AdoptionFormView.swift and remove the modifier from Text("Adopt Me")
. Add your new modifier below the trailing closure of alert(_:isPresented:actions:)
:
.buttonStyle(PrimaryButtonStyle())
Build and run. Your UI looks the same as before:
Apart from being able to add the button’s label styling, ButtonStyleConfiguration
has another property that you’ll learn how to use next.
Using the isPressed Property on Button Style
Right now, when you press the button, you can see the default opacity styling applied. You’ll use the isPressed
property to show custom styling when a user presses the button.
Open ViewModifiers.swift and in the PrimaryButtonStyle
struct replace the last two modifiers, .foregroundColor()
and .overlay()
, with the following:
.foregroundColor(
configuration.isPressed
? Color.mint.opacity(0.2)
: Color.pink
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(
configuration.isPressed
? Color.mint.opacity(0.2)
: Color.pink,
lineWidth: 1.5
)
)
In the code above, you use the configuration.isPressed
property to add a different style when the button gets pressed. It changes the label’s color and border’s stroke to mint and adds a 0.2 opacity
.
Build and run. Tapping the button changes the color to mint and applies an opacity modifier:
Now it’s time to go a step further and create a conditional modifier.
Conditional ViewModifiers
Conditional modifiers let you apply a modifier only when a certain condition is true. Unlike view extensions, they can have their own stored properties.
Open EmailValidator.swift. It has a simple regular expression to validate the email address:
You’ll create a conditional view modifier to apply a green border to the text field when the user enters a valid email address or apply a secondary color for a default state.
Creating a Conditional ViewModifier
EmailValidator
is already set up and connected to the code. You only need to create and apply the conditional modifier.
Open ViewModifiers.swift and replace // TODO: 5
with:
// 1
struct Validate: ViewModifier {
var value: String
var validator: (String) -> Bool
// 2
func body(content: Content) -> some View {
// 3
content
.border(validator(value) ? .green : .secondary)
}
}
Here’s a breakdown:
- You create a
Validate
view modifier with two stored properties.value
is of typeString
and it’ll hold an email address that the user enters in the text field.validator
is a closure that takes aString
and returns aBoolean
. - You add the required
body(content:)
method that returnssome View
to conform to theViewModifier
protocol. - Finally, you validate the value and apply a green border on the content if validator is
true
.
You can apply the modifier as-is, but you’ll create an extension on the text field for convenience as you did with previous modifiers.
Open ViewModifiers.swift and replace // TODO: 6
with:
extension TextField {
func validateEmail(
value: String,
validator: @escaping (String) -> (Bool)
) -> some View {
modifier(Validate(value: value, validator: validator))
}
}
Here, you have properties in the modifier. When you apply the modifier to your UI component, you need to specify these parameters.
Next, open AdoptionFormView.swift and add the following just below TextField("Email address", text: $emailValidator.email)
:
.validateEmail(value: emailValidator.email) { email in
emailValidator.isValid(email)
}
Make sure you add it as the first modifier on the text field for the border to work. Remember, modifier order matters.
In the code above, you apply the validateEmail(value:validator:)
on the text field. Then you call isValid()
in the closure and pass the email for validation.
Build and run. When you type inside the text field, it’ll validate the value as you type. Try typing yourname@mail.com, and you’ll see the border change to green:
When you create a view modifier by extending a view, you can supply default parameters. In the next section, you’ll see how this works by creating an Image
extension with default parameters.