Created
May 3, 2024 20:55
-
-
Save danielsaidi/612c2edd670d939ea5810b5f12b64f94 to your computer and use it in GitHub Desktop.
Swift Strict Concurrency - Before & After
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
// MARK: - BEFORE STRICT CONCURRENCY | |
// This was all the code in `DeckShuffleAnimation` before I made it support strict concurrency | |
public class DeckShuffleAnimation: ObservableObject { | |
public init( | |
maxDegrees: Double = 6, | |
maxOffsetX: Double = 6, | |
maxOffsetY: Double = 6 | |
) { | |
self.maxDegrees = maxDegrees | |
self.maxOffsetX = maxOffsetX | |
self.maxOffsetY = maxOffsetY | |
} | |
public let maxDegrees: Double | |
public let maxOffsetX: Double | |
public let maxOffsetY: Double | |
public var isShuffling: Bool { | |
!shuffleData.isEmpty | |
} | |
@Published | |
fileprivate var animationTrigger = false | |
@Published | |
private var shuffleData: [ShuffleData] = [] | |
public struct ShuffleData { | |
public let angle: Angle | |
public let xOffset: Double | |
public let yOffset: Double | |
} | |
} | |
public extension View { | |
func withShuffleAnimation<Item: DeckItem>( | |
_ animation: DeckShuffleAnimation, | |
for item: Item, | |
in items: [Item] | |
) -> some View { | |
let data = animation.shuffleData(for: item, in: items) | |
return self.rotationEffect(data?.angle ?? .zero) | |
.offset(x: data?.xOffset ?? 0, y: data?.yOffset ?? 0) | |
.animation(.default, value: animation.animationTrigger) | |
} | |
} | |
public extension DeckShuffleAnimation { | |
func shuffle<Item>( | |
_ items: Binding<[Item]>, | |
times: Int = 3 | |
) { | |
if animationTrigger { return } | |
randomizeShuffleData(for: items) | |
shuffle(items, times: times, time: 1) | |
} | |
} | |
private extension DeckShuffleAnimation { | |
func performAfterDelay(_ action: @escaping () -> Void) { | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: action) | |
} | |
func randomizeShuffleData<Item>( | |
for items: Binding<[Item]> | |
) { | |
shuffleData = (0..<items.count).map { _ in | |
ShuffleData( | |
angle: Angle.degrees(Double.random(in: -maxDegrees...maxDegrees)), | |
xOffset: Double.random(in: -maxOffsetX...maxOffsetX), | |
yOffset: Double.random(in: -maxOffsetY...maxOffsetY) | |
) | |
} | |
} | |
func shuffle<Item>( | |
_ items: Binding<[Item]>, | |
times: Int, | |
time: Int | |
) { | |
animationTrigger.toggle() | |
performAfterDelay { | |
if time < times { | |
self.randomizeShuffleData(for: items) | |
self.shuffle(items, times: times, time: time + 1) | |
} else { | |
self.easeOutShuffleState(for: items) | |
} | |
} | |
} | |
func shuffleData<Item: DeckItem>( | |
for item: Item, | |
in items: [Item] | |
) -> ShuffleData? { | |
guard | |
shuffleData.count == items.count, | |
let index = items.firstIndex(of: item) | |
else { return nil } | |
return shuffleData[index] | |
} | |
func easeOutShuffleState<Item>( | |
for items: Binding<[Item]> | |
) { | |
shuffleData = shuffleData.map { | |
ShuffleData( | |
angle: $0.angle/2, | |
xOffset: $0.xOffset/2, | |
yOffset: $0.yOffset/2 | |
) | |
} | |
animationTrigger.toggle() | |
performAfterDelay { | |
self.resetShuffleState(for: items) | |
} | |
} | |
func resetShuffleState<Item>( | |
for items: Binding<[Item]> | |
) { | |
animationTrigger.toggle() | |
shuffleData = [] | |
performAfterDelay { | |
items.wrappedValue.shuffle() | |
self.animationTrigger = false | |
} | |
} | |
} | |
// MARK: - AFTER STRICT CONCURRENCY | |
// This was the `DeckShuffleAnimation` after Strict Concurrency | |
@MainActor | |
public final class DeckShuffleAnimation: ObservableObject { | |
public init( | |
animation: Animation = .default, | |
maxDegrees: Double = 6, | |
maxOffsetX: Double = 6, | |
maxOffsetY: Double = 6 | |
) { | |
self.animation = animation | |
self.maxDegrees = maxDegrees | |
self.maxOffsetX = maxOffsetX | |
self.maxOffsetY = maxOffsetY | |
} | |
public let animation: Animation | |
public let maxDegrees: Double | |
public let maxOffsetX: Double | |
public let maxOffsetY: Double | |
public var isShuffling = false | |
@Published | |
private var shuffleData: [ShuffleData] = [] | |
} | |
private struct ShuffleData: Sendable { | |
public let angle: Angle | |
public let xOffset: Double | |
public let yOffset: Double | |
} | |
@MainActor | |
public extension View { | |
func deckShuffleAnimation<Item: DeckItem>( | |
_ animation: DeckShuffleAnimation, | |
for item: Item, | |
in items: [Item] | |
) -> some View { | |
let data = animation.shuffleData(for: item, in: items) | |
return self | |
.rotationEffect(data?.angle ?? .zero) | |
.offset(x: data?.xOffset ?? 0, y: data?.yOffset ?? 0) | |
} | |
} | |
public extension DeckShuffleAnimation { | |
func shuffle<Item>( | |
_ items: Binding<[Item]>, | |
times: Int? = nil | |
) { | |
Task { | |
await shuffleAsync(items, times: times) | |
} | |
} | |
func shuffleAsync<Item>( | |
_ items: Binding<[Item]>, | |
times: Int? = nil | |
) async { | |
if isShuffling { return } | |
isShuffling = true | |
let itemCount = items.count | |
for _ in 0...(times ?? 3) { | |
setRandomShuffleData(for: itemCount) | |
try? await Task.sleep(nanoseconds: 200_000_000) | |
} | |
items.wrappedValue.shuffle() | |
setShuffleData([]) | |
isShuffling = false | |
} | |
} | |
@MainActor | |
private extension DeckShuffleAnimation { | |
func setRandomShuffleData(for itemCount: Int) { | |
setShuffleData( | |
(0..<itemCount).map { _ in | |
.init( | |
angle: Angle.degrees(Double.random(in: -maxDegrees...maxDegrees)), | |
xOffset: Double.random(in: -maxOffsetX...maxOffsetX), | |
yOffset: Double.random(in: -maxOffsetY...maxOffsetY) | |
) | |
} | |
) | |
} | |
func setShuffleData(_ data: [ShuffleData]) { | |
withAnimation(animation) { | |
shuffleData = data | |
} | |
} | |
func shuffleData<Item: DeckItem>( | |
for item: Item, | |
in items: [Item] | |
) -> ShuffleData? { | |
guard | |
shuffleData.count == items.count, | |
let index = items.firstIndex(of: item) | |
else { return nil } | |
return shuffleData[index] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment