Skip to content

Commit e3aa422

Browse files
committed
Add support for custom gradient modes
1 parent 5659a62 commit e3aa422

File tree

3 files changed

+57
-8
lines changed

3 files changed

+57
-8
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ Text("Loading...")
5151
)
5252
```
5353

54+
## Gradient Mode
55+
56+
In addition to the original masking mode (which is still the default), Shimmer now supports additional modes to apply the gradient, including as a background or an overlay with a custom blend mode:
57+
58+
![Loading](docs/custom-gradient-mode.gif)
59+
60+
```swift
61+
Text("Custom Gradient Mode").bold()
62+
.font(.largeTitle)
63+
.shimmering(
64+
gradient: Gradient(colors: [.clear, .orange, .white, .green, .clear]),
65+
bandSize: 0.5,
66+
mode: .overlay()
67+
)
68+
```
69+
5470
## Animated Skeletons ☠️
5571

5672
Of course, you can combine `.shimmering(...)` with the `.redacted(...)` modifier to create interesting animated skeleton views.

Sources/Shimmer/Shimmer.swift

+41-8
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,19 @@ import SwiftUI
88

99
/// A view modifier that applies an animated "shimmer" to any view, typically to show that an operation is in progress.
1010
public struct Shimmer: ViewModifier {
11+
public enum Mode {
12+
/// Masks the content with the gradient (this is the usual, default mode).
13+
case mask
14+
/// Overlays the gradient with a given `BlendMode` (`.sourceAtop` by default).
15+
case overlay(blendMode: BlendMode = .sourceAtop)
16+
/// Places the gradient behind the content.
17+
case background
18+
}
19+
1120
private let animation: Animation
1221
private let gradient: Gradient
1322
private let min, max: CGFloat
23+
private let mode: Mode
1424
@State private var isInitialState = true
1525
@Environment(\.layoutDirection) private var layoutDirection
1626

@@ -23,13 +33,15 @@ public struct Shimmer: ViewModifier {
2333
public init(
2434
animation: Animation = Self.defaultAnimation,
2535
gradient: Gradient = Self.defaultGradient,
26-
bandSize: CGFloat = 0.3
36+
bandSize: CGFloat = 0.3,
37+
mode: Mode = .mask
2738
) {
2839
self.animation = animation
2940
self.gradient = gradient
3041
// Calculate unit point dimensions beyond the gradient's edges by the band size
3142
self.min = 0 - bandSize
3243
self.max = 1 + bandSize
44+
self.mode = mode
3345
}
3446

3547
/// The default animation effect.
@@ -65,23 +77,23 @@ public struct Shimmer: ViewModifier {
6577
/// The start unit point of our gradient, adjusting for layout direction.
6678
var startPoint: UnitPoint {
6779
if layoutDirection == .rightToLeft {
68-
return isInitialState ? UnitPoint(x: max, y: min) : UnitPoint(x: 0, y: 1)
80+
isInitialState ? UnitPoint(x: max, y: min) : UnitPoint(x: 0, y: 1)
6981
} else {
70-
return isInitialState ? UnitPoint(x: min, y: min) : UnitPoint(x: 1, y: 1)
82+
isInitialState ? UnitPoint(x: min, y: min) : UnitPoint(x: 1, y: 1)
7183
}
7284
}
7385

7486
/// The end unit point of our gradient, adjusting for layout direction.
7587
var endPoint: UnitPoint {
7688
if layoutDirection == .rightToLeft {
77-
return isInitialState ? UnitPoint(x: 1, y: 0) : UnitPoint(x: min, y: max)
89+
isInitialState ? UnitPoint(x: 1, y: 0) : UnitPoint(x: min, y: max)
7890
} else {
79-
return isInitialState ? UnitPoint(x: 0, y: 0) : UnitPoint(x: max, y: max)
91+
isInitialState ? UnitPoint(x: 0, y: 0) : UnitPoint(x: max, y: max)
8092
}
8193
}
8294

8395
public func body(content: Content) -> some View {
84-
content
96+
applyingGradient(to: content)
8597
.mask(LinearGradient(gradient: gradient, startPoint: startPoint, endPoint: endPoint))
8698
.animation(animation, value: isInitialState)
8799
.onAppear {
@@ -92,6 +104,18 @@ public struct Shimmer: ViewModifier {
92104
}
93105
}
94106
}
107+
108+
@ViewBuilder public func applyingGradient(to content: Content) -> some View {
109+
let gradient = LinearGradient(gradient: gradient, startPoint: startPoint, endPoint: endPoint)
110+
switch mode {
111+
case .mask:
112+
content.mask(gradient)
113+
case let .overlay(blendMode: blendMode):
114+
content.overlay(gradient.blendMode(blendMode))
115+
case .background:
116+
content.background(gradient)
117+
}
118+
}
95119
}
96120

97121
public extension View {
@@ -106,10 +130,11 @@ public extension View {
106130
active: Bool = true,
107131
animation: Animation = Shimmer.defaultAnimation,
108132
gradient: Gradient = Shimmer.defaultGradient,
109-
bandSize: CGFloat = 0.3
133+
bandSize: CGFloat = 0.3,
134+
mode: Shimmer.Mode = .mask
110135
) -> some View {
111136
if active {
112-
modifier(Shimmer(animation: animation, gradient: gradient, bandSize: bandSize))
137+
modifier(Shimmer(animation: animation, gradient: gradient, bandSize: bandSize, mode: mode))
113138
} else {
114139
self
115140
}
@@ -159,6 +184,14 @@ struct Shimmer_Previews: PreviewProvider {
159184
.font(.largeTitle)
160185
.shimmering()
161186
.environment(\.layoutDirection, .rightToLeft)
187+
188+
Text("Custom Gradient Mode").bold()
189+
.font(.largeTitle)
190+
.shimmering(
191+
gradient: Gradient(colors: [.clear, .orange, .white, .green, .clear]),
192+
bandSize: 0.5,
193+
mode: .overlay()
194+
)
162195
}
163196
}
164197
#endif

docs/custom-gradient-mode.gif

27.5 KB
Loading

0 commit comments

Comments
 (0)