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?
View Extensions With Default Parameters
AdoptAPet contains two Image
views with identical modifiers but different parameters. This is a perfect candidate to create a view extension modifier with default parameters.
Open PetCardView.swift and you’ll see four modifiers on the Image(pet.photo)
:
Next, open PetDetailedView.swift and you’ll see the same four modifiers again, but with different parameters for frame(maxWidth:maxHeight)
:
In the first view, there’s a maxWidth
of .infinity
. But the second one calculates it using geometry.size.width
.
You’ll move them to a new Image
extension and provide default parameters that can change when needed.
Creating an Image Modifier
Open ViewModifiers.swift and replace // TODO: 7
with:
extension Image {
// 1
func photoStyle(
withMaxWidth maxWidth: CGFloat = .infinity,
withMaxHeight maxHeight: CGFloat = 300
) -> some View {
// 2
self
.resizable()
.scaledToFill()
// 3
.frame(maxWidth: maxWidth, maxHeight: maxHeight)
.clipped()
}
}
Here’s a breakdown:
- You create an
Image
extension with a single method,photoStyle()
, that returnssome View
.photoStyle()
takes two parameters,maxWidth
andmaxHeight
. These parameters can differ across views, so you add default values. Both images hadmaxHeight
of 300 so that makes sense as the default. For themaxWidth
parameter, set the default value to.infinity
. -
self
refers to the specific view for which you are creating the extension. In this case, anImage
. When creating view extensions,self
comes in handy. Inside an extension method’s body, you can add different types of views, likeHStack
andVStack
, then useself
to indicate where the current view should go. - In the
.frame(maxWidth:maxHeight:)
modifier, you replace hardcoded values with the method parameters.
Next, open PetCardView.swift and replace all four modifiers below Image(pet.photo)
with:
.photoStyle()
There are no parameters specified as this image uses the default parameters.
Now, open PetDetailedView.swift and replace all four modifiers below the Image(pet.photo)
with:
.photoStyle(withMaxWidth: geometry.size.width)
To calculate the maxWidth
for this image, you need to use GeometryReader
, which provides size information about the parent view. So instead of using the default parameter like for maxHeight
, you add geometry.size.width
.
Build and run. If you’ve done everything right, your app will look exactly as before:
Amazing! You’ve now learned how to create your own view modifiers and view extensions.
You might be wondering when to use one over the other. There’s no correct answer.
One difference to remember is that view modifiers let you add stored properties while extensions to View
don’t.
Ready for a Challenge?
Are you feeling up for a challenge? Want to try and create a view extension on your own? This one is a bit more complex, but nothing you can’t do!
Open PetDetailedInformationView.swift. You’ve created a .detailedInfoTitle()
modifier for your titles and cut a few lines of code. But the view is still big.
Not only that, you repeat much of the code five times:
If you look closely, every HStack
is almost the same. The only differences are image name and information text. The entire point of the HStack
is to add an icon to the section as a prefix.
That’s exactly what your challenge is. Create a new Text
extension with a prefixedWithSFSymbol
modifier that takes an image name as a parameter. The idea is to create a modifier that’ll prefix the Text
with an SF Symbol. Use what you’ve learned above and try to do it yourself.
Once you complete the challenge, you can see a solution here:
[spoiler title=”Challenge Solution”]
extension Text {
func prefixedWithSFSymbol(named name: String) -> some View {
HStack {
Image(systemName: name)
.resizable()
.scaledToFit()
.frame(width: 17, height: 17)
self
}
.padding(.leading, 12)
}
}
[/spoiler]
Once you create the modifier, you need to apply it to Text
. This is how the modifier will change on Text(pet.breed)
:
[spoiler title=” Applying the modifier”]
[/spoiler]
Where to Go From Here?
Well done!
You’ve learned how to create custom SwiftUI view modifiers to make your views reusable and avoid repeating code.
You can download the completed project files by clicking the Download Materials button at the top or bottom of this tutorial.
There’s one SwiftUI view modifier that can come handy that wasn’t covered, called EmptyModifier. It’s used during development to switch modifiers during compile time.
If you want to learn more about how to configure your views, check the Configuring views section of Apple’s SwiftUI documentation.
Once you’ve learned more about view configuration, check out the App Design Apprentice book to learn more about designing modern mobile apps with attractive and effective UI and UX.
We hope you enjoyed this tutorial. If you have any questions or comments, feel free to drop them in the discussion below.