How can I make an element such as a text field scale up and then down continuously?
I have this:
struct ContentView : View {
@State var size:Double = 0.5
var body: some View {
ZStack {
Text("Hello!")
.padding()
.scaleEffect(size)
}
}
}
I know I need to increase size and then decrease it in some sort of loop but the following cannot be done in SwiftUI:
while true {
self.size += 0.8
sleep(0.2)
self.size -= 0.8
}
A possible solution is to use a (repeating, auto-reversing) animation:
struct ContentView : View {
@State var size: CGFloat = 0.5
var repeatingAnimation: Animation {
Animation
.easeInOut(duration: 2) //.easeIn, .easyOut, .linear, etc...
.repeatForever()
}
var body: some View {
Text("Hello!")
.padding()
.scaleEffect(size)
.onAppear() {
withAnimation(self.repeatingAnimation) { self.size = 1.3 }
}
}
}
Animation.basic is deprecated. Basic animations are now named after their curve types: like linear, etc:
var foreverAnimation: Animation {
Animation.linear(duration: 0.3)
.repeatForever()
}
Source: https://forums.swift.org/t/swiftui-animation-basic-duration-curve-deprecated/27076
The best way is to create separate animation struct and configure all the options you need(this way your code will be more compact).
To make it more clear and logical use @State property isAnimating
. You will be able to stop your animation and resume again and understand when it is in progress.
@State private var isAnimating = false
var foreverAnimation: Animation {
Animation.linear(duration: 0.3)
.repeatForever()
}
var body: some View {
Text("Hello")
.scaleEffect(isAnimating ? 1.5 : 1)
.animation(foreverAnimation)
.onAppear {
self.isAnimating = true
}
}
Using a repeating animation on a view has weird behaviour when used inside if statements.
If you want to do:
if something {
BlinkingView()
}
use a transition with an animation modifier, otherwise the view stays on the screen even after something
is set to false.
I made this extension to show a view that repeats change from one state to the next and back:
extension AnyTransition {
static func repeating<T: ViewModifier>(from: T, to: T, duration: Double = 1) -> AnyTransition {
.asymmetric(
insertion: AnyTransition
.modifier(active: from, identity: to)
.animation(Animation.easeInOut(duration: duration).repeatForever())
.combined(with: .opacity),
removal: .opacity
)
}
}
This makes the view appear and disappear with AnyTransition.opacity
and while it is shown it switches between the from
and to
state with a delay of duration
.
Example usage:
struct Opacity: ViewModifier {
private let opacity: Double
init(_ opacity: Double) {
self.opacity = opacity
}
func body(content: Content) -> some View {
content.opacity(opacity)
}
}
struct ContentView: View {
@State var showBlinkingView: Bool = false
var body: some View {
VStack {
if showBlinkingView {
Text("I am blinking")
.transition(.repeating(from: Opacity(0.3), to: Opacity(0.7)))
}
Spacer()
Button(action: {
self.showBlinkingView.toggle()
}, label: {
Text("Toggle blinking view")
})
}.padding(.vertical, 50)
}
}
Edit:
When the show condition is true on appear, the transition doesn't start. To fix this I do toggle the condition on appear of the superview (The VStack in my example):
.onAppear {
if self.showBlinkingView {
self.showBlinkingView.toggle()
DispatchQueue.main.async {
self.showBlinkingView.toggle()
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With