Skip to content

Instantly share code, notes, and snippets.

@markmals
Last active February 10, 2024 03:44
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markmals/075273b58a94db20917235fdd5cda3cc to your computer and use it in GitHub Desktop.
Save markmals/075273b58a94db20917235fdd5cda3cc to your computer and use it in GitHub Desktop.
The iOS Home Screen wiggle animation, in SwiftUI
import SwiftUI
extension View {
func wiggling() -> some View {
modifier(WiggleModifier())
}
}
struct WiggleModifier: ViewModifier {
@State private var isWiggling = false
private static func randomize(interval: TimeInterval, withVariance variance: Double) -> TimeInterval {
let random = (Double(arc4random_uniform(1000)) - 500.0) / 500.0
return interval + variance * random
}
private let rotateAnimation = Animation
.easeInOut(
duration: WiggleModifier.randomize(
interval: 0.14,
withVariance: 0.025
)
)
.repeatForever(autoreverses: true)
private let bounceAnimation = Animation
.easeInOut(
duration: WiggleModifier.randomize(
interval: 0.18,
withVariance: 0.025
)
)
.repeatForever(autoreverses: true)
func body(content: Content) -> some View {
content
.rotationEffect(.degrees(isWiggling ? 2.0 : 0))
.animation(rotateAnimation)
.offset(x: 0, y: isWiggling ? 2.0 : 0)
.animation(bounceAnimation)
.onAppear() { isWiggling.toggle() }
}
}
// An example app to demonstrate the wiggle effect
import SwiftUI
@main
struct WiggleApp: App {
var body: some Scene {
WindowGroup {
ZStack {
Color.white.ignoresSafeArea()
HStack(spacing: 30) {
CalendarView()
WeatherView()
}
}
}
}
}
struct CalendarView: View {
var body: some View {
Widget(color: .black) {
Text("Wednesday")
Text("5").font(.system(size: 33))
Spacer()
Text("No more events today")
.frame(width: 150, height: 45, alignment: .leading)
.multilineTextAlignment(.leading)
}
}
}
struct WeatherView: View {
let weatherBg = LinearGradient(
gradient: Gradient(colors: [Color.blue, Color.white]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
var body: some View {
Widget(background: weatherBg) {
Text("Wednesday")
Text("18°")
.font(.system(size: 44))
.fontWeight(.thin)
Spacer()
Image(systemName: "cloud.sun.fill")
Text("Partly Cloudy")
.frame(width: 150, height: 20, alignment: .leading)
Text("H:21° L:12°")
}
}
}
struct Widget<Content: View, Background: View>: View {
let content: Content
let background: Background
init(background: Background, @ViewBuilder content: () -> Content) {
self.background = background
self.content = content()
}
var body: some View {
ZStack {
VStack(alignment: .leading) { content }
.padding()
.background(background)
.cornerRadius(22)
.foregroundColor(.white)
}
.frame(width: 170, height: 170, alignment: .leading)
.overlay(
Image(systemName: "minus.circle.fill")
.font(.title)
.foregroundColor(Color(.systemGray))
.background(
Color.black
.clipShape(Circle())
.frame(width: 20, height: 20)
)
.offset(x: -80, y: -80)
)
.wiggling()
}
}
extension Widget where Background == Color {
init(color: Color, @ViewBuilder content: () -> Content) {
self.init(background: color, content: content)
}
}
struct WiggleApp_Previews: PreviewProvider {
static var previews: some View {
HStack(spacing: 30) {
CalendarView()
WeatherView()
}
.background(Color.white)
}
}
@rungxanh1995
Copy link

Thank you @markmals for sharing! This is very useful 💯

@TR-Godwin
Copy link

thanks, this was very helpful @markmals

@StefanBristol
Copy link

Note that 'animation' was depricated in iOS 15.0: Use withAnimation or animation(_:value:) instead.

@webserveis
Copy link

But how to switch start or stop animation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment