Skip to content

Instantly share code, notes, and snippets.

@danielsaidi
Created May 3, 2024 20:55
Show Gist options
  • Save danielsaidi/612c2edd670d939ea5810b5f12b64f94 to your computer and use it in GitHub Desktop.
Save danielsaidi/612c2edd670d939ea5810b5f12b64f94 to your computer and use it in GitHub Desktop.
Swift Strict Concurrency - Before & After
// 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