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

Animated assign subscriber #17

Merged
merged 8 commits into from
Jun 25, 2020
96 changes: 96 additions & 0 deletions Sources/AnimatedAssignSubscriber.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// AnimatedAssignSubscriber.swift
//
// Created by Marin Todorov on 5/3/20.
//

import Foundation
import Combine

#if canImport(UIKit)
import UIKit

/// A list of animations that can be used with `Publisher.assign(to:on:animation:)`
public enum AssignTransition {
public enum Direction {
case top, bottom, left, right
}

/// Flip from either bottom, top, left, or right.
case flip(direction: Direction, duration: TimeInterval)

/// Cross fade with previous value.
case fade(duration: TimeInterval)

/// A custom animation. Do not include your own code to update the target of the assign subscriber.
case animation(duration: TimeInterval, options: UIView.AnimationOptions, animations: () -> Void, completion: ((Bool) -> Void)?)
}

extension Publisher where Self.Failure == Never {
/// Behaves identically to `Publisher.assign(to:on:)` except that it allows the user to
/// "wrap" emitting output in an animation transition.
///
/// For example if you assign values to a `UILabel` on screen you
/// can make it flip over when each new value is set:
///
/// ```
/// myPublisher
/// .assign(to: \.text,
/// on: myLabel,
/// animation: .flip(direction: .bottom, duration: 0.33))
/// ```
///
/// You may also provide a custom animation block, as follows:
///
/// ```
/// myPublisher
/// .assign(to: \.text, on: myLabel, animation: .animation(duration: 0.33, options: .curveEaseIn, animations: { _ in
/// myLabel.center.x += 10.0
/// }, completion: nil))
/// ```
public func assign<Root: UIView>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root, animation: AssignTransition) -> AnyCancellable {
var transition: UIView.AnimationOptions
var duration: TimeInterval

switch animation {
case .fade(let interval):
duration = interval
transition = .transitionCrossDissolve
case let .flip(dir, interval):
duration = interval
switch dir {
case .bottom: transition = .transitionFlipFromBottom
case .top: transition = .transitionFlipFromTop
case .left: transition = .transitionFlipFromLeft
case .right: transition = .transitionFlipFromRight
}
case let .animation(interval, options, animations, completion):
// Use a custom animation.
return handleEvents(receiveOutput: { value in
UIView.animate(withDuration: interval,
delay: 0,
options: options,
animations: {
object[keyPath: keyPath] = value
animations()
},
completion: completion)
})
.sink { _ in }
}

// Use one of the built-in transitions like flip or crossfade.
return self
.handleEvents(receiveOutput: { value in
UIView.transition(with: object,
duration: duration,
options: transition,
animations: {
object[keyPath: keyPath] = value
},
completion: nil)
})
.sink { _ in }
}
}
#endif