unioil-loyalty-rn-app/ios/Pods/lottie-ios/Sources/Public/iOS/AnimatedSwitch.swift

234 lines
6.7 KiB
Swift

//
// AnimatedSwitch.swift
// lottie-swift
//
// Created by Brandon Withrow on 2/4/19.
//
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst)
import UIKit
/// An interactive switch with an 'On' and 'Off' state. When the user taps on the
/// switch the state is toggled and the appropriate animation is played.
///
/// Both the 'On' and 'Off' have an animation play range associated with their state.
open class AnimatedSwitch: AnimatedControl {
// MARK: Lifecycle
public override init(
animation: Animation,
configuration: LottieConfiguration = .shared)
{
/// Generate a haptic generator if available.
#if os(iOS)
if #available(iOS 10.0, *) {
self.hapticGenerator = HapticGenerator()
} else {
hapticGenerator = NullHapticGenerator()
}
#else
hapticGenerator = NullHapticGenerator()
#endif
super.init(animation: animation, configuration: configuration)
isAccessibilityElement = true
updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false)
}
public override init() {
/// Generate a haptic generator if available.
#if os(iOS)
if #available(iOS 10.0, *) {
self.hapticGenerator = HapticGenerator()
} else {
hapticGenerator = NullHapticGenerator()
}
#else
hapticGenerator = NullHapticGenerator()
#endif
super.init()
isAccessibilityElement = true
updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false)
}
required public init?(coder aDecoder: NSCoder) {
/// Generate a haptic generator if available.
#if os(iOS)
if #available(iOS 10.0, *) {
self.hapticGenerator = HapticGenerator()
} else {
hapticGenerator = NullHapticGenerator()
}
#else
hapticGenerator = NullHapticGenerator()
#endif
super.init(coder: aDecoder)
isAccessibilityElement = true
}
// MARK: Open
open override func animationDidSet() {
updateOnState(isOn: _isOn, animated: animateUpdateWhenChangingAnimation, shouldFireHaptics: false)
}
open override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
updateOnState(isOn: !_isOn, animated: true, shouldFireHaptics: true)
sendActions(for: .valueChanged)
}
// MARK: Public
/// Defines what happens when the user taps the switch while an
/// animation is still in flight
public enum CancelBehavior {
case reverse // default - plays the current animation in reverse
case none // does not update the animation when canceled
}
/// The cancel behavior for the switch. See CancelBehavior for options
public var cancelBehavior: CancelBehavior = .reverse
/// If `false` the switch will not play the animation when changing between animations.
public var animateUpdateWhenChangingAnimation = true
public override var accessibilityTraits: UIAccessibilityTraits {
set { super.accessibilityTraits = newValue }
get { super.accessibilityTraits.union(.button) }
}
/// The current state of the switch.
public var isOn: Bool {
set {
/// This is forwarded to a private variable because the animation needs to be updated without animation when set externally and with animation when set internally.
guard _isOn != newValue else { return }
updateOnState(isOn: newValue, animated: false, shouldFireHaptics: false)
}
get {
_isOn
}
}
/// Set the state of the switch and specify animation and haptics
public func setIsOn(_ isOn: Bool, animated: Bool, shouldFireHaptics: Bool = true) {
guard isOn != _isOn else { return }
updateOnState(isOn: isOn, animated: animated, shouldFireHaptics: shouldFireHaptics)
}
/// Sets the play range for the given state. When the switch is toggled, the animation range is played.
public func setProgressForState(
fromProgress: AnimationProgressTime,
toProgress: AnimationProgressTime,
forOnState: Bool)
{
if forOnState {
onStartProgress = fromProgress
onEndProgress = toProgress
} else {
offStartProgress = fromProgress
offEndProgress = toProgress
}
updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false)
}
// MARK: Internal
// MARK: Animation State
func updateOnState(isOn: Bool, animated: Bool, shouldFireHaptics: Bool) {
_isOn = isOn
var startProgress = isOn ? onStartProgress : offStartProgress
var endProgress = isOn ? onEndProgress : offEndProgress
let finalProgress = endProgress
if cancelBehavior == .reverse {
let realtimeProgress = animationView.realtimeAnimationProgress
let previousStateStart = isOn ? offStartProgress : onStartProgress
let previousStateEnd = isOn ? offEndProgress : onEndProgress
if
realtimeProgress.isInRange(
min(previousStateStart, previousStateEnd),
max(previousStateStart, previousStateEnd))
{
/// Animation is currently in the previous time range. Reverse the previous play.
startProgress = previousStateEnd
endProgress = previousStateStart
}
}
updateAccessibilityLabel()
guard animated == true else {
animationView.currentProgress = finalProgress
return
}
if shouldFireHaptics {
hapticGenerator.generateImpact()
}
animationView.play(
fromProgress: startProgress,
toProgress: endProgress,
loopMode: LottieLoopMode.playOnce,
completion: { [weak self] finished in
guard let self = self else { return }
// For the Main Thread rendering engine, we freeze the animation at the expected final progress
// once the animation is complete. This isn't necessary on the Core Animation engine.
if finished, !(self.animationView.animationLayer is CoreAnimationLayer) {
self.animationView.currentProgress = finalProgress
}
})
}
// MARK: Fileprivate
fileprivate var onStartProgress: CGFloat = 0
fileprivate var onEndProgress: CGFloat = 1
fileprivate var offStartProgress: CGFloat = 1
fileprivate var offEndProgress: CGFloat = 0
fileprivate var _isOn = false
fileprivate var hapticGenerator: ImpactGenerator
// MARK: Private
private func updateAccessibilityLabel() {
accessibilityValue = _isOn ? NSLocalizedString("On", comment: "On") : NSLocalizedString("Off", comment: "Off")
}
}
#endif
// MARK: - ImpactGenerator
protocol ImpactGenerator {
func generateImpact()
}
// MARK: - NullHapticGenerator
class NullHapticGenerator: ImpactGenerator {
func generateImpact() { }
}
#if os(iOS)
@available(iOS 10.0, *)
class HapticGenerator: ImpactGenerator {
// MARK: Internal
func generateImpact() {
impact.impactOccurred()
}
// MARK: Fileprivate
fileprivate let impact = UIImpactFeedbackGenerator(style: .light)
}
#endif