'Is there a better way to implement a shake animation in swiftui?
I'm trying to get a button to shake when the user tries to log in without filling all the textfields in, and this is what I've come across so far:
struct Shake: GeometryEffect {
var amount: CGFloat = 10
var shakesPerUnit = 3
var animatableData: CGFloat
func effectValue(size: CGSize) -> ProjectionTransform {
ProjectionTransform(CGAffineTransform(translationX:
amount * sin(animatableData * .pi * CGFloat(shakesPerUnit)),
y: 0))
}
}
struct Correct: View {
@State var attempts: Int = 0
var body: some View {
VStack {
Rectangle()
.fill(Color.pink)
.frame(width: 200, height: 100)
.modifier(Shake(animatableData: CGFloat(attempts)))
Spacer()
Button(action: {
withAnimation(.default) {
self.attempts += 1
}
}, label: { Text("Login") })
}
}
}
However, this is particularly useless for a button, and even then the animation seems very off in that its pretty robotic. Can someone suggest an improvement so that I can get my button to shake?
Solution 1:[1]
try this
struct ContentView: View {
@State var selected = false
var body: some View {
VStack {
Button(action: {
self.selected.toggle()
}) { selected ? Text("Deselect") : Text("Select") }
Rectangle()
.fill(Color.purple)
.frame(width: 200, height: 200)
.offset(x: selected ? -30 : 0)
.animation(Animation.default.repeatCount(5).speed(6))
}
}
}
Solution 2:[2]
I do this to make the field shake and then gets back to it's original position:
private func signUp() {
if email.isEmpty {
withAnimation {
emailIsWrong = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation {
emailIsWrong = false
}
}
return
}
}
Where emailIsWrong
is a @State variable:
@State private var emailIsWrong = false
Basically after 0.2 sec, I change the emailIsWrong
back to false so the view goes back to its position. My text field looks like this:
TextField("Email", text: $email)
.padding()
.frame(height: 45)
.background(Color.white)
.colorScheme(.light)
.offset(x: emailIsWrong ? -8 : 0)
.animation(Animation.default.repeatCount(3, autoreverses: true).speed(6))
Solution 3:[3]
here's my one
@State var ringOnFinish: Bool = false
@State var shakeOffset: Double = 0
Button() {
ringOnFinish.toggle()
//give it a little shake animation when off
if !ringOnFinish {
shakeOffset = 5
withAnimation {
shakeOffset = 0
}
} label: {
Image(systemName: "bell\(ringOnFinish ? "" : ".slash")")
.offset(x: ringOnFinish ? 0 : shakeOffset)
.animation(.default.repeatCount(3, autoreverses: true).speed(6), value: ringOnFinish)
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Chris |
Solution 2 | |
Solution 3 | Luke Price |