I spent a lot of time trying to figure out whether this was a bug, but I couldn't for the life of me figure out why a shape wasn't blurring correctly.
VStack {
//Doesn't blur right
Capsule()
.fill(LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing))
.frame(width: 80, height: 40)
.blur(radius: 10)
//Does blur correctly
HStack {
}
.frame(width: 80, height: 40)
.background(
LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)
)
.clipShape(Capsule())
.blur(radius: 10)
}
When you apply blur to an empty HStack, it blurs correctly, while using a blur to a shape doesn't. Can someone explain why it differs?
Firstly, two of them looks like the same but it's not the same.
From Apple docs, Capsule() is subclass of Shape which is subclass of View.
The first one, when you using Capsule() directly then you .fill means just like you add subview LinearGradient with colors into Capsule(). That's the reason I think .blur not working correctly when you call directly from Capsule().
The second one, you .blur correctly because the view was directly be drawn on view ( just like drawing in layer of view in normal UIKit)
The view hierarchy show the different between two of them.
The solution:
You can do like your second view
HStack {
}
.frame(width: 80, height: 40)
.background(
LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)
)
.clipShape(Capsule())
.blur(radius: 10)
Or you can make linear gradient with mask
HStack {
LinearGradient(gradient: Gradient(colors: [.red, .blue]), startPoint: .leading, endPoint: .trailing)
.mask(
Capsule()
.blur(radius: 10)
.frame(width: 80, height: 40)
)
}.frame(width: 200, height: 60)
Both will act the same like you draw on the layer of view
UPDATE
If you need it works directly, just use only one color then call .foregroundColor then blur will continue work - But only one color at the time only.
Capsule()
.foregroundColor(Color.red)
.blur(radius: 10)
.frame(width: 80, height: 40)
Related
UPDATE
I was able to get something closer to what I'm trying to achieve. Still needs work. I attempted to use a rounded rectangle for the mask but doesn't look good. Any suggestions would be greatly appreciated.
GeometryReader { geometry in
RoundedRectangle(cornerRadius: 7.5, style: .circular)
.fill(LinearGradient(gradient: Gradient(colors: [Color.green, Color.yellow, Color.orange, Color.red, Color.purple]), startPoint: .bottom, endPoint: .top))
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .bottomLeading)
.overlay(Color.black.opacity(0.35).cornerRadius(7.5))
RoundedRectangle(cornerRadius: 7.5, style: .circular)
.fill(LinearGradient(gradient: Gradient(colors: [Color.green, Color.yellow, Color.orange, Color.red, Color.purple]), startPoint: .bottom, endPoint: .top))
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .bottomLeading)
.mask(
VStack {
Spacer()
Rectangle()
// Adjust value 1 to needs
.frame(width: geometry.size.width, height:geometry.size.height * (CGFloat(1) / CGFloat(11)), alignment: .bottom)
})
}
.frame(width: 15, height: .infinity)
.padding(.all, 10)
Older
I'm attempting to create a simple gauge like the linear gauge in watchOS. The trouble I'm having is figuring out the proper way to overlay the current value. The outcome I'd like to see is below. I'm sure there's a better way.
Outcome
Mine look absolutely terrible
ZStack {
LinearGradient(gradient: Gradient(colors: [Color.green, Color.yellow, Color.orange, Color.red, Color.purple]), startPoint: .bottom, endPoint: .top)
GeometryReader { metrics in
Circle()
.stroke(Color.white,lineWidth: 2)
.frame(minHeight: 0, maxHeight: metrics.size.height)
.foregroundColor(Color.clear)
.position(x: metrics.size.width / 2 ,y: metrics.size.height * (CGFloat(10) / CGFloat(11)))
}
}
.cornerRadius(7.5)
.frame(minWidth: 0, maxWidth: 15)
I've solved my solution. I managed to create the same view by making a custom ProgressView style.
I would like to add a white text on the top of an image.
My strategy will be to add a gradient that will be blurred at the text zone (please check the attached picture)
Anyone got an idea how to do this?
How's this?
For the blur effect, just use the .blur() modifier - no need for a separate image that's blurred.
struct ContentView: View {
let gradient = LinearGradient(
gradient: Gradient(stops: [
.init(color: .purple, location: 0),
.init(color: .clear, location: 0.4)
]),
startPoint: .bottom,
endPoint: .top
)
var body: some View {
Image("Background")
.resizable()
.aspectRatio(contentMode: .fit)
.overlay(
ZStack(alignment: .bottom) {
Image("Background")
.resizable()
.blur(radius: 20) /// blur the image
.padding(-20) /// expand the blur a bit to cover the edges
.clipped() /// prevent blur overflow
.mask(gradient) /// mask the blurred image using the gradient's alpha values
gradient /// also add the gradient as an overlay (this time, the purple will show up)
HStack {
Image("Icon") /// app icon
.resizable()
.frame(width: 64, height: 64)
VStack(alignment: .leading) {
Text("Classroom of the Elite")
.bold()
Text("Horikita best girl")
.opacity(0.75)
}
.frame(maxWidth: .infinity, alignment: .leading) /// allow text to expand horizontally
Button { } label: {
Text("GET")
.bold()
.padding(8)
.background(Color.gray)
.cornerRadius(16)
}
}
.foregroundColor(.white)
.padding(20)
}
)
}
}
I'm trying to mask a RoundedRectangle (filled with a gradient) with some view to receive the following result:
The code I'm using is as follows:
VStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 10)
.fill(LinearGradient(gradient: self.gradient, startPoint: .leading, endPoint: .trailing))
.frame(width: geometry.size.width, alignment: .top)
.mask(
//WHAT TO PLACE HERE?
)
RoundedRectangle(cornerRadius: 10)
.fill(LinearGradient(gradient: self.gradient, startPoint: .leading, endPoint: .trailing))
.frame(width: geometry.size.width, height: 8)
}
However when putting Rectangle().frame(width: geometry.size.width * 0.4, alignment: .leading) inside the .mask, it ignores the given frame width and simply displays the full RoundedRectangle:
I've tried other views inside the .mask to check if the clipping works at all.
I tried Circle().frame(alignment: .leading), which resulted in:
Notice how this doesn't adhere to the given alignment.
Then I tried Text("MASKING MASKING").frame(alignment: .trailing), which resulted in:
Notice how this does adhere to the given alignment.
I'm very confused as to how I can go about achieving the desired result since SwiftUI masking behaviour seems very unintuitive.
Here is demo of solution. Tested with Xcode 11.4 / iOS 13.4
.mask(
VStack {
Rectangle().frame(width: geometry.size.width * 0.4)
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
)
I cannot figure out what compositingGroup() is. At first, I thought it is something like Merging layers in Photoshop. But it was not. Because .shadow() effects to the overlay and background views respectively even if I use .compositingGroup().
So far, I've found 2 differences when I use .compositingGroup()
Text doesn't have shadows.
The shadow size of the overlay view is slightly smaller than the above one.
What is the purpose of compositingGroup?
struct ContentView: View {
var body: some View {
VStack(spacing: 50) {
Text("Without\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.shadow(color: .blue, radius: 5)
Text("With\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.compositingGroup() // <--- I added .compositingGroup() here.
.shadow(color: .blue, radius: 5)
}
}
}
This modifier makes the following modifiers be applied to the view as a whole and not to each particular subview separately
Here's an example to better illustrate this:
struct ContentView: View {
let circles: some View = ZStack {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.red)
.offset(y: -25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.blue)
.offset(x: -25, y: 25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.green)
.offset(x: 25, y: 25)
}
var body: some View {
VStack(spacing: 100) {
circles
circles
.opacity(0.5)
circles
.compositingGroup()
.opacity(0.5)
}
}
}
So in your case the shadow is applied to the whole view rather than separately to the Text and overlaying RoundedRectangle
Use it when wanting to apply effects like opacity or shadow to a group of views and not each contained element by itself.
It seems like that .shadow() modifier will add both inner and outer shadow. It means that if the view is not "solid", for example, it has a "hole", .shadow() will add shadow like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.shadow(color: .blue, radius: 5)
Click to see the image
So, if you do not want the inner shadow, you need to make your view be "solid", like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.shadow(color: .blue, radius: 5)
Click to see the image
However, something goes wrong again, the inner shadow doesn't disappear.
That's because I forgot to apply the .compositingGroup() modifier.
As #ramzesenok mentioned, .compositingGroup() makes the following modifiers be applied to the view as a whole and not to each particular subview separately.
So, change the code a little bit:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.compositingGroup()
.shadow(color: .blue, radius: 5)
Click to see the image
There is only outer shadow now.
Was trying to blur my circle with a gradient can't figure out why it doesn't work. Anybody have ideas or is this not supported yet?
struct GradientExperiment: View {
var body: some View {
VStack {
//blur works
Circle()
.fill(Color(.blue))
.frame(width: 50, height: 50)
.blur(radius: 10)
//blur does not work
Circle()
.fill(LinearGradient(gradient: Gradient(colors: [.blue,.red]), startPoint: .bottom, endPoint: .top))
.frame(width: 50, height: 50)
.blur(radius: 10)
}
}
}
Add a mask to your LinearGradient and use the Circle as a mask:
LinearGradient(gradient: Gradient(colors: [.blue, .red]), startPoint: .bottom, endPoint: .top)
.mask(
Circle()
.frame(width: 50, height: 50)
.blur(radius: 10)
)