Last active
August 26, 2024 23:01
-
-
Save kieranb662/40e1faeffe3490d3a04910ec24f9298e to your computer and use it in GitHub Desktop.
[Rainbow ViewModifier] Rainbow animation view modifiers for SwiftUI #SwiftUI #ViewModifier
This file contains hidden or 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
// | |
// Rainbow.swift | |
// Created by Kieran Brown on 4/15/20. | |
import SwiftUI | |
struct Rainbow: ViewModifier { | |
let hueColors = stride(from: 0, to: 1, by: 0.1).map { | |
Color(hue: $0, saturation: 1, brightness: 1) | |
} | |
func body(content: Content) -> some View { | |
content | |
.overlay(GeometryReader { (proxy: GeometryProxy) in | |
ZStack { | |
LinearGradient(gradient: Gradient(colors: self.hueColors), | |
startPoint: .leading, | |
endPoint: .trailing) | |
.frame(width: proxy.size.width, height: proxy.size.height) | |
} | |
}) | |
.mask(content) | |
} | |
} | |
extension View { | |
func rainbow() -> some View { | |
self.modifier(Rainbow()) | |
} | |
} | |
struct RainbowAnimation: ViewModifier { | |
// 1 | |
@State var isOn: Bool = false | |
let hueColors = stride(from: 0, to: 1, by: 0.01).map { | |
Color(hue: $0, saturation: 1, brightness: 1) | |
} | |
// 2 | |
var duration: Double = 4 | |
var animation: Animation { | |
Animation | |
.linear(duration: duration) | |
.repeatForever(autoreverses: false) | |
} | |
func body(content: Content) -> some View { | |
// 3 | |
let gradient = LinearGradient(gradient: Gradient(colors: hueColors+hueColors), startPoint: .leading, endPoint: .trailing) | |
return content.overlay(GeometryReader { proxy in | |
ZStack { | |
gradient | |
// 4 | |
.frame(width: 2*proxy.size.width) | |
// 5 | |
.offset(x: self.isOn ? -proxy.size.width/2 : proxy.size.width/2) | |
} | |
}) | |
// 6 | |
.onAppear { | |
withAnimation(self.animation) { | |
self.isOn = true | |
} | |
} | |
.mask(content) | |
} | |
} | |
extension View { | |
func rainbowAnimation() -> some View { | |
self.modifier(RainbowAnimation()) | |
} | |
} | |
struct RainbowExamples: View { | |
var body: some View { | |
ZStack { | |
Color(white: 0.1).edgesIgnoringSafeArea(.all) | |
VStack { | |
Capsule() | |
.frame(width: 200, height: 75) | |
.rainbow() | |
RoundedRectangle(cornerRadius: 10) | |
.inset(by: 5) | |
.stroke(Color.black, lineWidth: 5) | |
.frame(width: 300, height: 100) | |
.rainbowAnimation() | |
Text("Rainbow") | |
.font(.system(size: 100)) | |
.rainbowAnimation() | |
} | |
} | |
} | |
} | |
struct Rainbow_Previews: PreviewProvider { | |
static var previews: some View { | |
RainbowExamples() | |
} | |
} |
And if you change it to :
.offset(x: self.isOn ? -proxy.size.width : 0)
it looks much nicer the animation is flowing normally and the colors don't come back at the middle.
I'm running this on XCode 13.1 and found some issues when attaching the .rainbow() modifier to a view nested inside of a NavigationView. The modified view slides in from the top left corner along with displaying the rainbow effect. To fix this I changed the following bit of code:
.onAppear {
withAnimation(self.animation) {
self.isOn = true
}
}
To the following:
.onAppear {
DispatchQueue.main.async {
withAnimation(self.animation) {
self.isOn = true
}
}
}
Not sure why extra animation is added to the parent view but wrapping withAnimation with async seems to fix the issue.
I updated the script to use iOS 17's withAnimation method. I also integrated @Viogenius and @akatzfey1's changes.
//
// Rainbow.swift
// Created by Kieran Brown on 4/15/20.
import SwiftUI
struct Rainbow: ViewModifier {
let hueColors = stride(from: 0, to: 1, by: 0.1).map {
Color(hue: $0, saturation: 1, brightness: 1)
}
func body(content: Content) -> some View {
content
.overlay(GeometryReader { (proxy: GeometryProxy) in
ZStack {
LinearGradient(gradient: Gradient(colors: self.hueColors),
startPoint: .leading,
endPoint: .trailing)
.frame(width: proxy.size.width, height: proxy.size.height)
}
})
.mask(content)
}
}
extension View {
func rainbow() -> some View {
self.modifier(Rainbow())
}
}
struct RainbowAnimation: ViewModifier {
@State private var isOn: Bool = false
let hueColors = stride(from: 0, to: 1, by: 0.01).map {
Color(hue: $0, saturation: 1, brightness: 1)
}
var duration: Double = 4
func body(content: Content) -> some View {
let gradient = LinearGradient(
gradient: Gradient(colors: hueColors + hueColors),
startPoint: .leading,
endPoint: .trailing
)
return content
.overlay(GeometryReader { proxy in
ZStack {
gradient
.frame(width: 2 * proxy.size.width)
.offset(x: self.isOn ? -proxy.size.width : 0)
}
})
.onAppear {
withAnimation(.linear(duration: self.duration).repeatForever(autoreverses: false)) {
self.isOn = true
}
}
.mask(content)
}
}
extension View {
func rainbowAnimation() -> some View {
self.modifier(RainbowAnimation())
}
}
struct RainbowExamples: View {
var body: some View {
ZStack {
Color(white: 0.1).edgesIgnoringSafeArea(.all)
VStack {
Capsule()
.frame(width: 200, height: 75)
.rainbow()
RoundedRectangle(cornerRadius: 10)
.inset(by: 5)
.stroke(Color.black, lineWidth: 5)
.frame(width: 300, height: 100)
.rainbowAnimation()
Text("Rainbow")
.font(.system(size: 100))
.rainbowAnimation()
}
}
}
}
struct Rainbow_Previews: PreviewProvider {
static var previews: some View {
RainbowExamples()
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Got it, just changed this line 👍
.offset(x: self.isOn ? -proxy.size.width/2 : proxy.size.width/2)
to
.offset(x: self.isOn ? -proxy.size.width/2 : 0)