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.
Related
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 {
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.
I want to have a easeIn/moveUp animation onAppear for the text information and SF symbols. Thereafter, I want only the SF symbols to scale up and down forever. Currently, I have managed to scale up down forever but when I managed to chain, the easeIn/moveUp animation also repeated itself forever which should only be repeated once when it appears.
This is how my app representation looks like:
I have included a snippet of the pin animation code below:
HStack (spacing: 20) {
Image(systemName: "pin")
.foregroundColor(.red)
.font(.title2)
.padding(.trailing, 5)
.scaleEffect(animationAmount)
.onAppear {
let baseAnimation = Animation.easeInOut(duration: 1)
let repeated = baseAnimation.repeatForever(autoreverses: true)
return withAnimation(repeated) {
self.animationAmount = 1.5
}
}
VStack (alignment: .leading) {
Text("Pin favourites").fontWeight(.semibold)
Text("You can pin your favourite content on all devices")
.foregroundColor(.gray)
}
Spacer()
}//HStack 2
The full code is in the following page: SwiftUI - in sheet have a fixed continue button that is not scrollable
You can join animation with value, like below
.scaleEffect(animationAmount)
.animation(Animation.easeInOut(duration: 1).repeatForever(autoreverses: true), value: animationAmount)
.onAppear {
self.animationAmount = 1.5
}
moreover, as now animation and state joined uniquely and will not conflict with other animations/states, you can move last two modifiers to the very top container (instead of repeating them three times)
For example here:
}//VStack for 3 criterias
.padding([.leading, .trailing], 20)
.animation(Animation.easeInOut(duration: 1).repeatForever(autoreverses: true), value: animationAmount)
.onAppear {
self.animationAmount = 1.5
}
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 :)
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.