Last active
July 24, 2024 09:53
-
-
Save markmals/075273b58a94db20917235fdd5cda3cc to your computer and use it in GitHub Desktop.
The iOS Home Screen wiggle animation, in SwiftUI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's a version updated for iOS 17 and with support for enabling/disabling the effect and specifying the amount of jiggling.