SwiftUI: Clip Views with ClipShape

When you want to clip one view with another, you might have tried options such as using a ZStack or overlay. Depending on what you are trying to achieve, clipShape modifier can be a lot cleaner to write and easy to use.

We will be mainly taking a look at clipShape in this article, but I will also include a quick comparison to other options such as mask.

Specifically, here is what I will be sharing with you

The basics of using clipShape,A comparison to mask modifier together with couple examples in using mask, andSome of the cool (in my super personal opinion) animations we can make with clipShape!

Let’s go!

ClipShape Basics

Let’s start by observing the basic usage of the clipShape modifier.

Image(systemName: “heart.fill”)
.resizable()
.scaledToFit()
.frame(width: 100)
.padding(.all, 30)
.background(.red)
.clipShape(
Circle()
)

Yap! That’s it!

However, you might be wondering why don’t we just use the background modifier instead as the following will provide the exact same effect?

Image(systemName: “heart.fill”)
.resizable()
.scaledToFit()
.frame(width: 100)
.padding(.all, 30)
.background(Circle().fill(.red))

In the example above, yes, but this is not the beauty of the clipShape modifier!

Let’s remove the padding to check out the difference.

Image(systemName: “heart.fill”)
.resizable()
.scaledToFit()
.frame(width: 100)
.background(.red)
.clipShape(
Circle()
)

Image(systemName: “heart.fill”)
.resizable()
.scaledToFit()
.frame(width: 100)
.background(Circle().fill(.red))

Yes, just like the names of the modifiers suggest. The background is just a background, while the clipShape will actually clip the view (just like what we expect)!

Comparison To Mask

Another approach you might be considering in using is the mask modifier.

Again, back to our heart example.

Image(systemName: “heart.fill”)
.resizable()
.scaledToFit()
.frame(width: 100)
.background(.red)
.clipShape(
Circle()
)

Image(systemName: “heart.fill”)
.resizable()
.scaledToFit()
.frame(width: 100)
.background(.red)
.mask(
Circle()
)

And Again, exactly the same effect. Then what is the mask for (or what is the clipShape for)?

By using mask we can apply the alpha (opacity) value of another view to the current view, mask an image using text and many more. However, with clipShape, we are only adjusting the outside shape of the view.

Then why do we even need clipShape?

This is just my personal opinion/feeling and I have not actually measured it so do NOT quote on me. BUT I do feel like mask is heavier processing-wise when comparing to clipShape. By that means, if all you need is to clip the shape of a view with another, go for the clipShape option!

Now, let’s take look at couple examples in using mask before we move on!

Mask a View with View

For example, to have our heart masked by a Circle with a 20% opacity. We can do the following.

Image(systemName: “heart.fill”)
.resizable()
.scaledToFit()
.frame(width: 100)
.background(.red)
.mask(
Circle()
.opacity(0.2)
)

You might be wondering, can’t we achieve the same thing with clipShape? No, at least not within my ability!

First of all, we cannot specify opacity using clipShape. The following will give you an Instance method ‘clipShape(_:style:)’ requires that ‘some View’ conform to ‘Shape’ error.

.clipShape(
Circle()
.opacity(0.8)
)

Then why don’t we just apply the alpha individually to the foregroundStyle and background?

Image(systemName: “heart.fill”)
.resizable()
.scaledToFit()
.frame(width: 100)
.foregroundStyle(.black.opacity(0.2))
.background(.red.opacity(0.2))
.clipShape(
Circle()
)

Unfortunately, as yo can see below, since we have set the foregroundStyle to also have an alpha of 20%, you will get that background red color showing up!

Mask a View with Text

This is great when you want your Text to take a specific foreground style that is hard to apply by directly using foregroundStyle modifier.

For example, ITSUKI with a tiled heart background (Many loves to myself!) and ITSUKI with a Gradient background!

Image(“heart”)
.resizable(resizingMode: .tile)
.imageScale(.medium)
.frame(width: 300, height: 300)
.foregroundStyle(.red)
.mask(
Text(“ITSUKI”)
.font(.system(size: 92, weight: .bold))
)

RadialGradient(colors: [.yellow, .mint, .red, .orange, .black, .gray], center: .center, startRadius: 0, endRadius: 200)
.padding()
.frame(width: 350, height: 350)
.mask(
Text(“ITSUKI”)
.font(.system(size: 92, weight: .bold))
)

Definitely a lot of cool things you can do with mask! But not what we are focusing in this article so I will end it here and let’s head to create some interesting animations with clipShape!

Time for the Cool Animations

If you decide to read this article, and made it all the way down here, this is probably what you are waiting for!

First of all, DISCLAIMER!

*******

I think it is cool but I dont’ guarantee that you feel the same way! If that’s cool with you, please keep reading!

*******

ONE

Thinking back to those games where you are in the absolute dark and trying to read a line of hint with a tiny light moving around?

We can make it with clipShape with ease!

import SwiftUI
struct ClipViewDemo: View {
private let phases: [CGFloat] = [-3, -2, -1, 0, 1, 2, 3, 2, 1, 0, -1, -2] var body: some View {
VStack(spacing: 10) {
PhaseAnimator(phases, content: {phase in
Text(“some hints… And some other hints”)
.padding()
.background(.white)
.clipShape(
Circle()
.scale(0.8)
.offset(CGSize(width: phase*30.0, height: Int(phase)%2 == 0 ? -5 : 5))
)
}, animation: {phase in
.linear(duration: 1)
})
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray.opacity(0.2))
}
}

Everything is pretty straight forward here except for one thing that I would like to point out.

You might be wondering why do we have that scale effect in our Circle. If we don’t, you will recognize a clip over the sides like following.

You might be wondering if keep the scale to be 1.0 and add another padding() after clipShape will work? The answer is, does not work out for me!

Also, if you are not planning on moving the Circle close to the edges, you will not have to worry about this. If you want to move in an even larger range or want the circle to show up larger, you will have to adjust the padding and the scale accordingly

Two

Zooming out effect for revealing the entire content!

import SwiftUI
struct ClipViewDemo: View {
@State private var scale: CGFloat = 0.2

var body: some View {
VStack(spacing: 10) {
Text(“Some MemonSome More Memon More! More! More!”)
.multilineTextAlignment(.center)
.lineSpacing(10.0)
.frame(width: 300, height: 200)
.background(RoundedRectangle(cornerRadius: 16).fill(.yellow.opacity(0.3)))
.clipShape(
Circle()
.scale(scale)
)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now()+1, execute: {
withAnimation(.linear(duration: 5), {
scale = 2
})
})
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray.opacity(0.2))
}
}

I really like what I got here so let’s call it today!

Thank you for reading!

Happy clipping!

SwiftUI: Clip Views with ClipShape was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.

​ Level Up Coding – Medium

about Infinite Loop Digital

We support businesses by identifying requirements and helping clients integrate AI seamlessly into their operations.

Gartner
Gartner Digital Workplace Summit Generative Al

GenAI sessions:

  • 4 Use Cases for Generative AI and ChatGPT in the Digital Workplace
  • How the Power of Generative AI Will Transform Knowledge Management
  • The Perils and Promises of Microsoft 365 Copilot
  • How to Be the Generative AI Champion Your CIO and Organization Need
  • How to Shift Organizational Culture Today to Embrace Generative AI Tomorrow
  • Mitigate the Risks of Generative AI by Enhancing Your Information Governance
  • Cultivate Essential Skills for Collaborating With Artificial Intelligence
  • Ask the Expert: Microsoft 365 Copilot
  • Generative AI Across Digital Workplace Markets
10 – 11 June 2024

London, U.K.