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.

4.7 (3) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

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):

Set of modifiers on Image component

Next, open PetDetailedView.swift and you’ll see the same four modifiers again, but with different parameters for frame(maxWidth:maxHeight):

Set of modifiers on Image component

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:

  1. You create an Image extension with a single method, photoStyle(), that returns some View. photoStyle() takes two parameters, maxWidth and maxHeight. These parameters can differ across views, so you add default values. Both images had maxHeight of 300 so that makes sense as the default. For the maxWidth parameter, set the default value to .infinity.
  2. self refers to the specific view for which you are creating the extension. In this case, an Image. When creating view extensions, self comes in handy. Inside an extension method’s body, you can add different types of views, like HStack and VStack, then use self to indicate where the current view should go.
  3. 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:

AdoptAPet app running on iPhone 12 Pro Simulator showing the list of pets and detailed information about a selected pet

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:

Repeated set of modifiers in a HStack prefixing a title with a different system image

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”]
Code change after creating a new prefixedWithSFSymbol modifier with old code on the left and a new modifier applied to Text on the right
[/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.