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 } } } }