Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Remove IfElse To Keep State Correct #18

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 64 additions & 8 deletions Sources/Shimmer/Shimmer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import SwiftUI

/// A view modifier that applies an animated "shimmer" to any view, typically to show that an operation is in progress.
public struct Shimmer: ViewModifier {
private let isActive: Bool
private let animation: Animation
private let gradient: Gradient
private let min, max: CGFloat
Expand All @@ -21,10 +22,12 @@ public struct Shimmer: ViewModifier {
/// - bandSize: The size of the animated mask's "band". Defaults to 0.3 unit points, which corresponds to
/// 30% of the extent of the gradient.
public init(
isActive: Bool,
animation: Animation = Self.defaultAnimation,
gradient: Gradient = Self.defaultGradient,
bandSize: CGFloat = 0.3
) {
self.isActive = isActive
self.animation = animation
self.gradient = gradient
// Calculate unit point dimensions beyond the gradient's edges by the band size
Expand Down Expand Up @@ -82,11 +85,37 @@ public struct Shimmer: ViewModifier {

public func body(content: Content) -> some View {
content
.mask(LinearGradient(gradient: gradient, startPoint: startPoint, endPoint: endPoint))
.animation(animation, value: isInitialState)
.mask(
LinearGradient(
gradient: isActive ? gradient : .transparent,
startPoint: startPoint,
endPoint: endPoint
)
)
.animation(
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
value: isInitialState
)
.onAppear {
isInitialState = false
if isActive {
startShimmering()
}
}
.valueChanged(value: isActive) { isActive in
if isActive {
startShimmering()
} else {
stopShimmering()
}
}
}

private func startShimmering() {
isInitialState = false
}

private func stopShimmering() {
isInitialState = true
}
}

Expand All @@ -104,11 +133,9 @@ public extension View {
gradient: Gradient = Shimmer.defaultGradient,
bandSize: CGFloat = 0.3
) -> some View {
if active {
modifier(Shimmer(animation: animation, gradient: gradient, bandSize: bandSize))
} else {
self
}
modifier(
Shimmer(isActive: active, animation: animation, gradient: gradient, bandSize: bandSize)
)
}

/// Adds an animated shimmering effect to any view, typically to show that an operation is in progress.
Expand All @@ -130,6 +157,32 @@ public extension View {

#if DEBUG
struct Shimmer_Previews: PreviewProvider {
struct TogglePreview: View {
@State private var isShimmeringActive = true

var body: some View {
Form {
Toggle(isOn: $isShimmeringActive) {
Text("Is shimmering active")
}
ViewWithItsOwnState()
.shimmering(active: isShimmeringActive)
}
}

struct ViewWithItsOwnState: View {
@State private var isOn: Bool = false

var body: some View {
Toggle(isOn: $isOn) {
Text("Should remain the same when toggle shimmering")
}
Text("SwiftUI Shimmer")
.foregroundColor(.red)
}
}
}

static var previews: some View {
Group {
Text("SwiftUI Shimmer")
Expand All @@ -155,6 +208,9 @@ struct Shimmer_Previews: PreviewProvider {
.font(.largeTitle)
.shimmering()
.environment(\.layoutDirection, .rightToLeft)

TogglePreview()
.previewDisplayName("Shimmering toggle")
}
}
#endif
14 changes: 14 additions & 0 deletions Sources/Shimmer/Utility/Gradient+Convenience.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Gradient+Convenience.swift
//
//
// Created by Vladyslav Sosiuk on 06.11.2023.
//

import SwiftUI

extension Gradient {
static var transparent: Self {
.init(colors: [.black]) // .black has no effect
}
}
22 changes: 22 additions & 0 deletions Sources/Shimmer/Utility/View+iOS13Support.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// View+iOS13Support.swift
//
//
// Created by Vladyslav Sosiuk on 06.11.2023.
//

import SwiftUI
import Combine

extension View {
/// A backwards compatible wrapper for iOS 14 `onChange` based on https://betterprogramming.pub/implementing-swiftui-onchange-support-for-ios13-577f9c086c9
@ViewBuilder func valueChanged<T: Equatable>(value: T, onChange: @escaping (T) -> Void) -> some View {
if #available(iOS 14.0, *) {
self.onChange(of: value, perform: onChange)
} else {
self.onReceive(Just(value)) { (value) in
onChange(value)
}
}
}
}