@@ -8,6 +8,7 @@ import SwiftUI
88
99/// A view modifier that applies an animated "shimmer" to any view, typically to show that an operation is in progress.
1010public struct Shimmer : ViewModifier {
11+ private let isActive : Bool
1112 private let animation : Animation
1213 private let gradient : Gradient
1314 private let min , max : CGFloat
@@ -21,10 +22,12 @@ public struct Shimmer: ViewModifier {
2122 /// - bandSize: The size of the animated mask's "band". Defaults to 0.3 unit points, which corresponds to
2223 /// 30% of the extent of the gradient.
2324 public init (
25+ isActive: Bool ,
2426 animation: Animation = Self . defaultAnimation,
2527 gradient: Gradient = Self . defaultGradient,
2628 bandSize: CGFloat = 0.3
2729 ) {
30+ self . isActive = isActive
2831 self . animation = animation
2932 self . gradient = gradient
3033 // Calculate unit point dimensions beyond the gradient's edges by the band size
@@ -82,11 +85,35 @@ public struct Shimmer: ViewModifier {
8285
8386 public func body( content: Content ) -> some View {
8487 content
85- . mask ( LinearGradient ( gradient: gradient, startPoint: startPoint, endPoint: endPoint) )
86- . animation ( animation, value: isInitialState)
88+ . mask (
89+ LinearGradient (
90+ gradient: isActive ? gradient : . transparent,
91+ startPoint: startPoint,
92+ endPoint: endPoint
93+ )
94+ )
95+ . animation (
96+ isActive ? animation : . linear( duration: 0 ) , // FW: This is a correct way to stop animation. If using `isActive ? .linear(...) : .none` instead then the animation would be choppy. https://stackoverflow.com/questions/61830571/whats-causing-swiftui-nested-view-items-jumpy-animation-after-the-initial-drawi/61841018#61841018
97+ value: isInitialState
98+ )
8799 . onAppear {
88- isInitialState = false
100+ startShimmering ( )
89101 }
102+ . valueChanged ( value: isActive) { isActive in
103+ if isActive {
104+ startShimmering ( )
105+ } else {
106+ stopShimmering ( )
107+ }
108+ }
109+ }
110+
111+ private func startShimmering( ) {
112+ isInitialState = false
113+ }
114+
115+ private func stopShimmering( ) {
116+ isInitialState = true
90117 }
91118}
92119
@@ -104,11 +131,9 @@ public extension View {
104131 gradient: Gradient = Shimmer . defaultGradient,
105132 bandSize: CGFloat = 0.3
106133 ) -> some View {
107- if active {
108- modifier ( Shimmer ( animation: animation, gradient: gradient, bandSize: bandSize) )
109- } else {
110- self
111- }
134+ modifier (
135+ Shimmer ( isActive: active, animation: animation, gradient: gradient, bandSize: bandSize)
136+ )
112137 }
113138
114139 /// Adds an animated shimmering effect to any view, typically to show that an operation is in progress.
@@ -130,6 +155,32 @@ public extension View {
130155
131156#if DEBUG
132157struct Shimmer_Previews : PreviewProvider {
158+ struct TogglePreview : View {
159+ @State private var isShimmeringActive = true
160+
161+ var body : some View {
162+ Form {
163+ Toggle ( isOn: $isShimmeringActive) {
164+ Text ( " Is shimmering active " )
165+ }
166+ ViewWithItsOwnState ( )
167+ . shimmering ( active: isShimmeringActive)
168+ }
169+ }
170+
171+ struct ViewWithItsOwnState : View {
172+ @State private var isOn : Bool = false
173+
174+ var body : some View {
175+ Toggle ( isOn: $isOn) {
176+ Text ( " Should remain the same when toggle shimmering " )
177+ }
178+ Text ( " SwiftUI Shimmer " )
179+ . foregroundColor ( . red)
180+ }
181+ }
182+ }
183+
133184 static var previews : some View {
134185 Group {
135186 Text ( " SwiftUI Shimmer " )
@@ -155,6 +206,9 @@ struct Shimmer_Previews: PreviewProvider {
155206 . font ( . largeTitle)
156207 . shimmering ( )
157208 . environment ( \. layoutDirection, . rightToLeft)
209+
210+ TogglePreview ( )
211+ . previewDisplayName ( " Shimmering toggle " )
158212 }
159213}
160214#endif
0 commit comments