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)
}
)
}
}
Related
I am new to SwiftUI and trying to play around with some stuff.
I want to have a ZStack so I can have a background color with a see through black color that should animate on the opacity. On top of that I want to animate a "card view" in from the bottom. This is not possible currently with the code I have managed to use. Both background color and the card view is animating on the opacity - from invisible to visible.
How do I animate these two items inside the ZStack with a different transition?
Here is what I have done so far but without the correct result:
ZStack {
Color.black
.ignoresSafeArea()
.opacity(animateSmileys ? 0.3 : 0)
.animation(.easeInOut)
.zIndex(0)
VStack {
Spacer()
VStack(spacing: 30) {
Text("Hvordan har du det i dag?")
.font(.title2)
.fontWeight(.heavy)
.foregroundColor(Color("playerTopView"))
HStack {
ForEach(Array(moods.enumerated()), id: \.offset) { index, mood in
SmileyView(bgColor: mood.bgColor, smileyName: mood.smileyName)
.aspectRatio(contentMode: .fit)
.scaleEffect(CGFloat(animateSmileys ? 1 : 0), anchor: .center)
.rotationEffect(.degrees(animateSmileys ? 0 : 180), anchor: .center)
.animation(.interpolatingSpring(stiffness: 150, damping: 10).speed(2).delay(Double(index) * 0.2), value: animateSmileys)
}
}
HStack {
Spacer()
}
}
.padding()
.padding(.top, 20)
.background(Color("backgroundColor"))
.clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous))
.shadow(radius: 20)
}
.padding()
.transition(.move(edge: .bottom))
.animation(.spring())
.onAppear {
animateSmileys = true
}
.zIndex(1)
}
Hope you guys can help me!
I tried the above code and moving the .transition(.move(edge: .bottom)) and .animation(.spring()) from the VStack holding the "card view" to the VStack inside the VStack.
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)
I try to draw a squared Image inside of a Circle to get something like that (without blue square here. It is just to show image squared border):
This code makes squared image over all circle.
ZStack() {
Circle()
.fill(.orange)
Image(systemName: "trash")
.resizable()
.foregroundColor(.blue)
.background(.orange)
}
This code makes a small image on the circle or no circle at all:
ZStack() {
Circle()
.fill(.orange)
Image(systemName: "trash")
.foregroundColor(.blue)
.background(.orange)
}
I try to find a solution to have squared image inside the circle. It should not goes outside of it.
Maybe, I would also need a small margin between image and circle's border.
Is there a way to do that easily? Or I have to use math to get circle border or something like that?
Asperi just beat me to it, but this view will just take an icon name and a radius and return a squared icon perfectly in a circle:
struct ImageOnCircle: View {
let icon: String
let radius: CGFloat
var squareSide: CGFloat {
2.0.squareRoot() * radius
}
var body: some View {
ZStack {
Circle()
.fill(.orange)
.frame(width: radius * 2, height: radius * 2)
Image(systemName: icon)
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.frame(width: squareSide, height: squareSide)
.foregroundColor(.blue)
}
}
}
Use:
ImageOnCircle(icon: "trash", radius: 150)
Here is a demo of possible approach - use overlay + geometry reader to calculate internal rectangle where image is injected.
Tested with Xcode 13 / iOS 15 (blue rect is of Preview one for selected image)
Circle()
.fill(.orange)
.overlay(GeometryReader {
let side = sqrt($0.size.width * $0.size.width / 2)
VStack {
Rectangle().foregroundColor(.clear)
.frame(width: side, height: side)
.overlay(
Image(systemName: "trash")
.resizable()
.foregroundColor(.blue)
)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
})
.frame(width: 100, height: 100)
I have a ZStack in which an Image is presented.
The image needs to be scaled in some cases.
I am failing on aligning the scaled image on the bottom of the ZStack, it is always presented in the middle.
I tried ZStack(alignment: .bottom) in combination with .alignmentGuide(.bottom) for the image, but this does not change the outcome.
Also putting a VStack around the image and placing a Spacer() above it does not change the result.
The HStack is not relevant and is only shown, because I need an ZStack in this construct. But The main issue is with the VStack, that it does not move after scaling in the Space of the ZStack.
It seems like .scaleEffect just uses position and frame of the original image and places the scaled image in the middle. Is this a limitation of scaleEffect? What other function can be used?
This is my View (reduced code): // I colored the background purple, to show the full size of the ZStack
var body: some View {
ZStack(alignment: .bottom) {
Color.purple
Image(battlingIndividual.getMonster().getStatusImageName(battlingIndividual.status))
.resizable()
.scaledToFill()
.scaleEffect(battlingIndividual.getMonster().size.scaleValue)
HStack() {
SkillViews(battlingIndividual: battlingIndividual)
Spacer()
}
}
}
The outcome is this:
But it should look like this:
EDIT: I added a Background to the image, in order to show that the image is centered in the ZStack.
Solution:
We donĀ“t need an alignment in this case, we need an anchor:
.scaleEffect(battlingIndividual.getMonster().size.scaleValue, anchor: .bottom)
Solution Image:
I figured it out.
.scaleEffect uses its own anchor, which can be set to .bottom.
scaleEffect(_:anchor:) Apple Developer
Therefore I needed only to add "ancor: .bottom" to the scaleEffect.
.scaleEffect(battlingIndividual.getMonster().size.scaleValue, anchor:
.bottom)
for the following result:
I assume this view container ZStack is a one cell view, so you need to align not ZStack which tights to content, but entire HStack containing those monster cells, like
HStack(alignment: .bottom) { // << here !!
ForEach ... {
MonsterCellView()
}
}
Please, put your Image in a VStack and a Spacer() above the image and your Images will be on the bottom of the Stack. The alignment .bottom is only to aline multiple views with each other, but you are not having multiple views in your Stack. The HStack doesn't count for the alignment.
If I try this out in my example and scale the image down,
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.purple
VStack {
Spacer()
Image(systemName: "ladybug")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100, alignment:
HStack {
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Spacer()
}
}
}
}
}
I get this.
Kind regards,
MacUserT
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)
)