How to animate overlay in SwiftUI - ios

I have a button with an overlay of a rounded rectangle with a corner radius and stroked with a lineWidth of 1.0. This is being done in a custom ButtonStyle.
func makeBody(configuration: Configuration) -> some View {
configuration.label
.background(Color.green)
.cornerRadius(5)
.scaleEffect(configuration.isPressed ? 0.9 : 1)
.animation(.easeOut)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.blue, lineWidth: 1)
)
}
The issue with the method configuration above is that when the button is tapped, it animates the button except the overlay. The border here stays there and doesn't animate with the content of the button. Anyone know why/how to fix it?

Well, the answer is simple. The order of modifiers matter. The following actually solved the issue. Pretty much moved the overlay as the first modifier.
func makeBody(configuration: Configuration) -> some View {
configuration.label
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(borderStrokeColor, lineWidth: borderLineWidth)
)
.background(color)
.cornerRadius(cornerRadius)
.scaleEffect(configuration.isPressed ? 0.9 : 1)
.animation(.easeOut)
}
The animation works smoothly as it should :)

Related

SwiftUI rotation animation has unexpected translation [duplicate]

This question already has answers here:
SwiftUI: Broken explicit animations in NavigationView?
(2 answers)
Closed 9 months ago.
I'm trying to animate a spinning "busy" image in a SwiftUI View using standard and consistent online tutorials. The image is square, should be scaled uniformly, and spin forever.
I can get a spinning image with the following code, however I am seeing unexpected translations with this. When used in a NavigationView as the initial View the image moves over the course of the animation from top-left to the center of the screen. When used as the third screen in a NavigationView the image starts roughly in the center of the screen and moves vertically downwards by about half its height over the course of the animation. Removing the navigationBar and back button reduce the vertical shift to about 20px overall. Which is interesting.
Also interestingly, the background stays exactly where I'd expect it to be, in the center of the screen in both cases. autoreverses behaves as expected and anchoring the rotation effect seems to have no effect.
The aim is to have additional controls on the screen (text, Next button etc) but for now I'd be happy with an animation that stays in one place.
What am I doing wrong? Any help appreciated!
The code:
#State private var isAnimating = false
var body: some View {
VStack{
Spacer()
Image("FilmCircle")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: isAnimating ? 360 : 0), anchor: .center)
.animation(Animation.linear(duration: 2)
.repeatForever(autoreverses: false))
.onAppear {
self.isAnimating = true
}
.background(Color.red)
Spacer()
}
}
Declare .backgound(Color.red) before declaring animation so that it will animate background as well.
And for issue when using navigation view, see the code : -
VStack{
Spacer()
Image("FilmCircle")
.resizable()
.scaledToFit()
.background(Color.red)
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: isAnimating ? 360 : 0), anchor: .center)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation(.linear(duration: 2).repeatForever(autoreverses: false)) {
isAnimating = true
}
}
}
Spacer()
}
Hope you found this helpful.

SwiftUI Button is not grayed out when tapping

I am really trying to understand something.
I want to create a button with some text on it. The button have a frame and a color (color is same as background). When I tap that button I want the some effect to produce (some kind of discolouration to entire button). If the color is the same as the background just the text is coloured different during the tap. If I have a button with red background the effect is produced to the whole button. I have attached a picture on how things should look.
Before tap button should look like this:
When the user press the button (without release) the button looks like this:
In my case the red rectangle color is the same as the background color.But I want to have the same rectangle overlay. Instead of this I obtain the following.
Before tap:
During the tap:
In this case only the number has some sort of overlay. But the rectangle not.
Here is the code. What should I change to obtain the same feedback if the button is coloured the same as the background?
Button(action: {
some action
}){
Text("1")
.font(.title)
.foregroundColor(.blue)
.frame(width: 80, height: 48)
.background(Color.white)
.cornerRadius(4)
}
I cannot say that understood what exactly you want to achieve, but different visual feedback on button pressed can be done with help of button style. Within a style we have access to isPressed state, so can conditionally change presentation of button depending on that.
Here is some demo that should be helpful
Button(action: {
some action
}){
Text("1")
}
.buttonStyle(MyButtonStyle())
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.font(.title)
.foregroundColor(configuration.isPressed ? .white : .blue) // << ex. !!
.frame(width: 80, height: 48)
.background(configuration.isPressed ? Color.clear : Color.white) // << ex. !!
.cornerRadius(4)
}
}
I did not understand the question very well, however what I can tell is that you have a button that has a background the same as your main background, and you want the effect of when the user press the button, why not adding some Shadow?
Button(action: {
}){
Text("1")
.font(.title)
.foregroundColor(.blue)
.frame(width: 80, height: 48)
.background(Color.white)
.cornerRadius(4)
.shadow(color: Color.black.opacity(0.5), radius: 2, x: 0, y: 0)
}
The result as you said:
I understood your question. I think button pressed effect adopts color according to background color so I have just hacked this trick. In my code I have just used one ZStack and give white background color like this :
struct ContentView: View {
var body: some View {
ZStack{
Color.red
ZStack{
Rectangle().fill(Color.white)
Button(action: {
// some action
}){
Text("1")
.font(.title)
.frame(width: 80, height: 48, alignment: .center)
.background(Color.red)
}//.buttonStyle(MyButtonStyle()) // Set style if you want custom color on press event and remove background color from text.
}.frame(width: 80, height: 48, alignment: .center).cornerRadius(4)
}
}
}
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.background(configuration.isPressed ? Color.blue.opacity(0.5) : Color.red)
}
}
If you want to add your own color on the press then set button style and remove the background color from Text.
Hope this useful :)

SwiftUI hiding navigation bar affects other animations

I have the following SwiftUI view and I want to have it repeat the scale animation to simulate a pulsing effect.
However, in addition to the scaling, the circle is also moving up and down. It seems like the reason is because I set navigationBarHidden to true, which causes the circle to shift down and that shifting animation repeats together with the scaling animation. Removing the navigationBarHidden line would fix the issue but I would like to hide the navigation bar.
How can I make sure the repeat animation here only applies to the scalingEffect and make sure the animation doesn't get affected when hiding the navigation bar? Any help would be greatly appreciated!
#State var animate = false
var body: some View {
ZStack {
Circle()
.frame(width: 200, height: 200, alignment: .center)
.scaleEffect(animate ? 0.7 : 1.0, anchor: .center)
.animation(Animation.default.repeatForever(autoreverses: true))
}
.onAppear {
self.animate.toggle()
}
.navigationBarHidden(true)
}
Make animation per-value on state, like
Circle()
.frame(width: 200, height: 200, alignment: .center)
.scaleEffect(animate ? 0.7 : 1.0, anchor: .center)
.animation(Animation.default.repeatForever(autoreverses: true),
value: animate) // << here !!
I had the same issue as the whole view moving up and down when its child view is hidden and shown (animation) repeatedly.

SwiftUI ScrollView BlendMode Issues

I have run into an issue with SwiftUI and ScrollView. When I have a subview with a blend mode attribute in a scrollview, the blend does not affect what is behind the scrollview. But if I put the same item in a Stack, it does affect the background.
I tried to apply the blend mode directly to the scrollview, but since there are other items in the subview that are not blended, it messes that up.
Here is a sample to illustrate the issue. with this, you can see the gradient blend works in the Stack but not in the scrollview.
let mode: BlendMode = .overlay
func item(_ num: Int) -> some View {
return AnyView(
ZStack {
Rectangle()
.fill( LinearGradient(gradient: Gradient(colors: [Color.blue, Color.black]), startPoint: .top, endPoint: .bottom))
.frame(width: 100, height: 100)
.blendMode(mode)
Text("Number = \(num)")
.font(.headline)
.foregroundColor(Color.white)
}
)
}
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.fill(RadialGradient(gradient: Gradient(colors: [Color.black, Color.red]), center: .topLeading, startRadius: 200, endRadius: 800))
HStack (spacing: 50) {
VStack (spacing: 50) {
ForEach((1...5).reversed(), id: \.self) {
item($0)
}
}
ScrollView {
VStack (spacing: 50) {
ForEach((1...5).reversed(), id: \.self) {
item($0)
}
}
}
//.blendMode(mode)
/*
blendMode for item() in ScrollView does not work.
Uncomment blendMode modifier above to get gradient blend, but text is not correct
*/
}
}
}
}
I have also tried to apply the black-to-red Rectangle as a background of the Scrollview, and that doesn't fix it, either.
ScrollView {
VStack (spacing: 50) {
ForEach((1...5).reversed(), id: \.self) {
item($0)
}
}
}
.background(
Rectangle()
.fill(RadialGradient(gradient: Gradient(colors: [Color.black, Color.red]), center: .topLeading, startRadius: 200, endRadius: 800))
)
In the pictures below, the look I want is in the left VStack.
No blend mode modifier on ScrollView
Blend mode modifier applied to Scrollview
Any idea how to get around this?
Only thing I could think, which seems very hacky, was to basically create two scrollviews z-stacked with blend applied to only the scrollview needing it and then put the items not blended in the topmost scrollview.
However, I don't think that is viable as I see no way to sync scrolling across the two scrollviews.
I've come across a similar issue and from experimenting it seems as though the blend modes are affected when the SwiftUI View with the blend mode set is directly inside a UIHostingView (such as a ScrollView).
You can get the correct blending by placing another SwiftUI view below the one with the blend mode inside the scroll view, e.g.
ScrollView {
ZStack {
Rectangle()
.fill(RadialGradient(gradient: Gradient(colors: [Color.black, Color.red]), center: .topLeading, startRadius: 200, endRadius: 800))
VStack (spacing: 50) {
ForEach((1...5).reversed(), id: \.self) {
item($0)
}
}
}
}
This doesn't quite fix the issue in your example as the two backgrounds are not quite aligned, but in a case where the scroll view takes up the screen it might help.
It seems that adding a .background to the scroll view adds it below the ScrollView rather than at the bottom of the hierarchy in the ScrollView which is why that doesn't seem to fix it.
My issue was in a List row (which is backed by a UITableView), and setting an explicit background on the item inside the List helped there. Hopefully this will point you/others in the right direction.

Subview have parent's shadow, even with a background

So I just started developing with SwiftUI and I'm running in a small problem. Subviews are also displaying superview's shadow, even if the superview has a background. Does someone know how to fix this?
HStack {
HStack {
[...]
}
.padding(.leading, 12.0)
.padding(.trailing, 4.0)
.padding(.vertical, 16.0)
.background(Color("lightGreen"))
.cornerRadius(10)
}
.padding(8)
.background(Color.white)
.shadow(color: Color("tabShadow"), radius: 0.0, x: 0.0, y: -0.5)
.shadow(color: Color("tabShadow"), radius: 0.0, x: 0.0, y: 0.5)
As stated, the first HStack's shadow shouldn't be replicated into the child one, but it is. Only the first one though. Any hints?
Certain modifiers, when placed on a stack, are inherited by all their children. For instance, if you have a stack containing a bunch of Text views, you can place one .font() modifier on the stack and they will all be modified.
It appears that .shadow() is one of those modifiers. As to why only one is inherited, I suspect that the designers of SwiftUI don't expect .shadow() to be called more than once on a particular view, and didn't test for that.
If you are just trying to get a colored line across the top and bottom of the view, maybe try something like
.background(Color.white)
.background(Color("tabShadow").offset(x: 0, y: -0.5))
.background(Color("tabShadow").offset(x: 0, y: 0.5))
I am also newbie in swiftUI , but I think the problem is related to modifiers order and the fact that they change the View type.
I was able to solve the problem by adding .background(Color.white) and .cornerRadius(2.0) modifiers just before the shadow modifier and that applied the changes in parent (not children) View.
struct TestSwiftUIView: View {
var body: some View {
VStack {
Text("Hello World")
Text("Hello World")
}
.padding()
.background(Color.white)
.cornerRadius(2.0)
.shadow(radius: 3)
}
}
You can try overlay and background tricks when you have to make them render in multiple passes. In above case, the overlay will not be affected by the shadow or other effects.
If you think they are subViews, actually, they just render after the superView. It's a 2D world. So the overlay will be quite independently.
The only problem is just the size of overlay.
The hidden() is used here to occupy the position of invisible overlay. It's very cool if you master these layout skills.
struct ContentView: View {
var body: some View {
HStack {
SubContentView().hidden()
}
.padding(8)
.background(Color.white)
.shadow(color: Color.red, radius: 0, x: 0.0, y: -0.5)
.shadow(color: Color.red, radius: 0, x: 0.0, y: 0.5)
.overlay(SubContentView())
}
}
struct SubContentView: View {
var body: some View {
HStack{
Text("a")
Text("b")
Text("c")
Text("a")
Text("b")
Text("c")
}.padding(.leading, 12.0)
.padding(.trailing, 4.0)
.padding(.vertical, 16.0)
.background(Color.green)
.cornerRadius(10)
}
}
If you want to prevent the shadow the be applied to subviews then use the shadow modifier only inside the .background modifier like so:
VStack {
...
}.background(Color.white.shadow(color: .black.opacity(0.3), radius: 1, x: 1, y: 1))
Just Wrap your main view with some View and setShadow(1) to that view. and setShadow(0) to your main view. it overrides the parents shadow.
Lets say you have:
VStack{
Text("1")
Text("2")
}
and you want to set shadow to this VStack. So, wrap this VStack with another VStack (any View), and setShadow to that stack. Inner shadow overrides outer shadow. Finally your code should be:
VStack{
VStack{
Text("1")
Text("2")
}.shadow(radius: 0)
}
.shadow(radius: 1)

Resources