SwiftUI hiding navigation bar affects other animations - uinavigationbar

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.

Related

SwiftUI Animations: Why is the container view moving? I'm struggling to understand this behavior

I'm building a simple rotating circle loading animation. I've got the circle rotating but the container view is also moving downward for the duration of the animation. I can't figure out what could be causing this strange behavior.
Example:
The White border is the Parent View ZStack. The Red border is a VStack that I placed to show this weird animation oddity. The Blue border is a ZStack that holds the two Circles that animate.
Here is the code:
struct CustomLoadingView: View {
let viewWidth: CGFloat = UIScreen.main.bounds.width * 0.5
let backgroundCircleLineWidth: CGFloat = UIScreen.main.bounds.width * 0.025
let foregroundCircleLineWidth: CGFloat = UIScreen.main.bounds.width * 0.02
#State var rotationDegree: Angle = Angle.degrees(0)
var body: some View {
VStack(spacing: 0) {
ZStack {
Circle()
.stroke(style: StrokeStyle(lineWidth: backgroundCircleLineWidth))
.fill(Global.Colors.primary60)
Circle()
.trim(from: 0, to: 0.15)
.stroke(style: StrokeStyle(lineWidth: 7, lineCap: .round))
.fill(Global.Colors.secondary50)
.rotationEffect(self.rotationDegree)
}
.frame(width: viewWidth, height: viewWidth)
.border(Color.blue)
.animation(Animation.linear(duration: 4.5).repeatForever(autoreverses: false), value: rotationDegree)
.onAppear() {
self.animateLoader()
}
}
.border(Color.red)
}
func animateLoader() {
self.rotationDegree = .degrees(720)
}
}
Any idea as to why this is happening and how I can get it to stop? Thanks.
I figured it out with the help of vacawama who linked to a thread in the comments of the question. That thread also had an answer with another helpful link as well. Those were essentially the same problem as I was having here.
Here's the short and sweet:
It boils down to implicit vs explicit animations. In my code, here, I'm using an "explicit" animation. While using an "implicit" animation (withAnimation()) prevents the problem.
Apparently this may have something to do with how the View is being animated if the View is being navigated to. As was my case, I'm showing this View upon navigation. So the "navigation animation" was getting mixed in with my explicit animation. And, by using an implicit animation, SwiftUI is smart enough to figure out that the navigation animation is not supposed to be part of it.
Here's the code:
var body: some View {
ZStack {
Circle()
.stroke(style: StrokeStyle(lineWidth: backgroundCircleLineWidth))
.fill(Global.Colors.primary60)
Circle()
.trim(from: 0, to: 0.15)
.stroke(style: StrokeStyle(lineWidth: foregroundCircleLineWidth, lineCap: .round))
.fill(Global.Colors.secondary50)
.rotationEffect(self.rotationDegree)
}
.frame(width: viewWidth, height: viewWidth)
.onAppear() {
DispatchQueue.main.async {
withAnimation(Animation.linear(duration: 2.5).repeatForever(autoreverses: false)) {
self.rotationDegree = .degrees(720)
}
}
}
}
I just run the animation inside a withAnimation() block. And it now works as expected.

onDrag preview isn’t just of the dragged view but also displays the background the view is laid on top of

I’m using an .onDrag modifier on a view that has rounded corners:
struct RootView: View {
#State var dragging: Bool = false
var body: some View {
VStack {
Color.red.cornerRadius(32)
.frame(width: 300, height: 300)
.onDrag {
dragging = true
return NSItemProvider(object: NSString())
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.green)
}
}
The problem is when I invoke a drag - due to the automatic drop shadow effect applied by the system - it doesn’t feel as though I’m dragging the card on its own. See below.
Is there any way to get the card to look dragged with a transparent background, as opposed to above whatever background colour it was laid on top of?
There is content shape type for this,
Color.red.cornerRadius(32)
.frame(width: 300, height: 300)
.contentShape(.dragPreview, RoundedRectangle(cornerRadius: 32)) // << here !!
.onDrag {

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