Created
June 15, 2025 12:55
-
-
Save mdb1/60dfd39fae233152ddcc01e569bab253 to your computer and use it in GitHub Desktop.
Skeleton Modifier
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
import SwiftUI | |
extension View { | |
/// Adds a loading skeleton effect by masking the subviews when `condition` is true. | |
/// If you want to add skeleton to all the subviews in a container (like a Stack), | |
/// apply skeleton to the Stack itself. | |
/// | |
/// Apply to the whole view | |
/// ================== | |
/// If you need to apply the effect to the whole view instead of only to the subviews, | |
/// create a conditional overlay color and apply the skeleton to it: | |
/// | |
/// ```swift | |
/// view | |
/// .overlay(Color.gray) | |
/// .skeleton() | |
/// ``` | |
@ViewBuilder | |
func skeleton(condition: Bool = true) -> some View { | |
if condition { | |
modifier(Shimmer()) | |
} else { | |
self | |
} | |
} | |
} | |
struct Shimmer: ViewModifier { | |
@State private var isInitialState = true | |
func body(content: Content) -> some View { | |
content | |
.redacted(reason: .placeholder) | |
.mask( | |
LinearGradient( | |
gradient: Gradient( | |
colors: [ | |
.white.opacity(0.9), | |
.white.opacity(0.6), | |
.white.opacity(0.5), | |
.white.opacity(0.3), | |
.white.opacity(0), | |
.white.opacity(0.3), | |
.white.opacity(0.6), | |
.white.opacity(0.9), | |
] | |
), | |
startPoint: isInitialState ? .init(x: -1.0, y: 0) : .init(x: 1, y: 1), | |
endPoint: isInitialState ? .init(x: 0, y: 0) : .init(x: 1.9, y: 1) | |
) | |
) | |
.animation( | |
.easeIn(duration: 1.25) | |
.delay(0.25) | |
.repeatForever(autoreverses: false), | |
value: isInitialState | |
) | |
.onAppear { | |
// Needed to prevent a random bug in SwiftUI that makes this view | |
// to move randomly while the animation is on. | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { | |
isInitialState = false | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment