Giter Site home page Giter Site logo

jtrivedi / wave Goto Github PK

View Code? Open in Web Editor NEW
2.0K 16.0 53.0 8.07 MB

Wave is a spring-based animation engine for iOS and macOS that makes it easy to create fluid, interruptible animations that feel great.

Home Page: https://jtrivedi.github.io/Wave/

License: MIT License

Swift 99.67% Shell 0.33%
animation gestures ios motion swift interaction-design uikit ui appkit swiftui

wave's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wave's Issues

Allow an animation not to require an explicit restart after the target is reached then updated again.

This framework is great and I found it easy to replace Facebook pop โ€“ thank you!

One minor issue I have is that animations automatically stop when the target value is reached, then become inactive. Any further change to the target value is ignored until the animation is explicitly restarted. But an app may not care whether the animation has reached its target and may just want to animate changes by updating the target.

Facebook pop has a removedOnCompletion property to address this and keep animations alive when set to false. Would it be possible to add something similar?

Major wave update

I created a major wave update.. I never submitted a request, so please help me with it.

Extended animation support

  • NSView/UIView:
    • frame, size, origin, center, alpha, backgroundColor, borderColor, borderWidth, shadowColor, shadowOpacity, shadowOffset, shadowRadius, transform, scale, rotation, translation, cornerRadius
  • CALayer
    • frame, bounds, size, origin, center, opacity, backgroundColor, borderColor, borderWidth, shadowColor, shadowOpacity, shadowOffset, shadowRadius, transform, scale, rotation, translation, cornerRadius
  • NSTextField/UITextField, UILabel, UITextView:
    • fontSize, textColor
  • NSScrollView/UIScrollView:
    • contentOffset, zoomFactor
  • NSWindow:
    • frame, size, alpha, backgroundColor
  • NSLayoutConstraint:
    • constant

Spring

  • Feature, properties and functions parity to SwiftUI's Spring.
  • smooth, bouncy, snappy presets
  • Spring(duration: CGFloat, bounce: CGFloat)

SpringAnimator

  • Now uses AnimatableData instead of SpringInterpolatable.
  • Double, Float, CGFloat, CGPoint, CGSize, CGRect, CATransform3D, WaveColor, CGColor, CGAffineTransform support AnimatableData by default.
public protocol AnimatableData: Equatable, Comparable {
    /// The type defining the data to animate.
    associatedtype AnimatableData: VectorArithmetic = Self
    /// The data to animate.
    var animatableData: AnimatableData { get }
    /// Initializes with animatable data.
    init(_ animatableData: AnimatableData)
    /// Scaled integral representation of the value.
    var scaledIntegral: Self { get }
    static var zero: Self { get }
}

##AnimatablePropertyProvider protocol
Extending a class with AnimatablePropertyProvider adds a animator: PropertyAnimator<Class> property.

To set/get a property animated use the keyPath on the animator.

extension NSView: AnimatablePropertyProvider { }

Wave.animate(withSpring: .bouncy) {
        myView.animator[\.frame] = newFrame // sets a new frame animated.
        let currentFrame = myView.animator[\.frame] // current frame (either the target of the spring animation or the frame)
}

This allows modularity. E.g.

extension NSView: AnimatablePropertyProvider { }

extension PropertyAnimator<NSView> {
    var frame: CGRect {
        get { self[\.frame] }
        set { self[\.frame] = newValue }
    }
}

myView.animator.frame = newFrame

extension CALayer: AnimatablePropertyProvider { }

extension PropertyAnimator<CALayer> {
    var opacity: CGFloat {
        get { self[\.opacity] }
        set { self[\.opacity] = newValue }
    }
}

myLayer.animator.opacity = 0.5

Non-animated updates shouldn't wait until the next turn of the run loop

let v = UIView()
v.bounds.size = CGSize(width: 50, height: 50)
v.animator.scale = CGPoint(x: 0.5, y: 0.5)

print(v.frame.size)

This will print (50, 50) instead of (25, 25) until the next turn of the run loop. We shouldn't require the display link to fire to update things non-animatedly.

Animation delays should be interruptible

Wave animation delays shouldn't use dispatch_after. Instead, the engine should track delays internally so an animation's delay can be modified after the fact.

Improve `backgroundColor` animation retargeting

The current implementation of animating backgroundColor with the block-based API really isn't correct. We need to fully decompose the initial and target color values into their RGBA components, and animate each one on their own spring. That way, if you retarget a color animation, the carryover velocities will actually be correct.

HSL color space interpolation for UIColors

Optionally using HSL for color interpolation will provide nicer visual results when blending between colors, as compared to RGB. Specifically, this will help reduce muddy mid-interpolation hues.

Support for non-spring animations?

This is an awesome library. Thanks for building it.

I was wondering - Do you have any plans for supporting non-spring animations (i.e. ease-in-out or linear)? Or are they already there and I missed it?

Thanks.

Animators in SwiftUI

Really exciting that we're able to use Wave in SwiftUI. Couple of questions:

Unexpected animator state in completion block

Hi ๐Ÿ‘‹

The state of an animator is being updated after calling its completion block, resulting in being incorrect in said completion block.
This is major issue when you want to share a completion block with multiple animators with different settling times, and execute some logic after all animators have completed.

For example:

let firstAnimator = SpringAnimator<CGFloat>(...)
let secondAnimator = SpringAnimator<CGRect>(...)

let sharedCompletion = {
    guard case .ended = firstAnimator.state, case .ended = secondAnimator.state  // Will never be satisfied
    else { return }
    print("Both animations are finished.")
}

firstAnimator.completion = { completion in
    guard case .finished = completion else { return }
    // (firstAnimator.state == .ended) โ†’ false ๐Ÿ˜ญ
    sharedCompletion()
}

secondAnimator.completion = { completion in
    guard case .finished = completion else { return }
    // (secondAnimator.state == .ended) โ†’ false ๐Ÿ˜ญ
    sharedCompletion()
}

I understand that you don't accept contributions yet so I haven't opened a pull request, but if you change your mind, I don't mind opening one!
I forked the project and fixed the issue in the meantime.

Thanks again for this delightful animation engine, I love it!

DisplayLinkProvider preferredFrameRateRange range error

Hi,

First of all - great library! I am trying this out with SwiftUI (although I think this issue is unrelated) but I am encountering an issue. On my physical device (iPhone 11 Pro), the returned value of UIScreen.main.maximumFramesPerSecond is 61.

This makes the following error out:

        if #available(iOS 15.0, *) {
            let maximumFramesPerSecond = Float(UIScreen.main.maximumFramesPerSecond)
            let highFPSEnabled = maximumFramesPerSecond > 60
            let minimumFPS: Float = highFPSEnabled ? 80 : 60
            displayLinkProvider?.preferredFrameRateRange = .init(minimum: minimumFPS, maximum: maximumFramesPerSecond, preferred: maximumFramesPerSecond)
        }

Error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'invalid range (minimum: 80.00 maximum: 61.00 preferred: 61.00)'

For now, I am forking the repo to increase the highFPSEnabled threshold and see if that works. Is there any other way to make the check work more reliably?

More SwiftUI uses?

This isn't an issue just a request. Is it possible to recreate the AppKit demo in SwiftUI? I've been unsuccessful in my attempts, not sure if this is possible yet? Adding more SwiftUI examples would be greatly appreciated. I'd really enjoy adding this to my application to give it the pizazz it needs but haven't successfully found a use just yet.

Should ubiquitous extensions be made public?

Right now, the library ships with a few public extensions of built-in types that are probably already implemented by clients. For example + and - operators on CGPoint. These extensions will compete with any in-house extension a client defines itself in a standalone helper library, and could cause friction at best, or undefined behaviors at worse.

Of course, these extensions are very nice, but is this the purpose of this library to provide such functionality? If these extensions are not required for the public API of the library (I haven't experimented with it yet), I suppose they should be kept internal.

Animation must have a non-nil `value` before starting.

I get this error when I try to use this in swiftui: "Animation must have a non-nil value before starting."
my code is here:

`.onAppear {
offsetAnimator.value = .zero

        // The offset animator's callback will update the `offset` state variable.
        offsetAnimator.valueChanged = { newValue in
            boxOffset = newValue
        }
    }
    .offset(x: boxOffset.x, y: boxOffset.y)
    .gesture(
        DragGesture()
            .onChanged { value in
                // Update the animator's target to the new drag translation.
                offsetAnimator.target = CGPoint(x: value.translation.width, y: value.translation.height)

                // Don't animate the box's position when we're dragging it.
                offsetAnimator.mode = .nonAnimated
                offsetAnimator.start()
            }
            .onEnded { value in
                // Animate the box to its original location (i.e. with zero translation).
                offsetAnimator.target = .zero

                // We want the box to animate to its original location, so use an `animated` mode.
                // This is different than the
                offsetAnimator.mode = .animated

                // Take the velocity of the gesture, and give it to the animator.
                // This makes the throw animation feel natural and continuous.
                offsetAnimator.velocity = CGPoint(x: value.velocity.width, y: value.velocity.height)
                offsetAnimator.start()
            }
    )`

thank you for your help

macOS version

I don't know how feasible this would be as you use a lot of UIKit, but what about some level of AppKit support? I don't mean Catalyst support as I imagine that may work out of the box.

Maybe next week Apple will announce something to better bridge the gap.

If this is something you don't ever want to support, feel free to close this. Awesome work!

Animation type names conflict with SwiftUI

Animation conflicts with SwiftUI.Animation, and Wave.Animation doesn't resolve correctly, since Wave is also a type name within the module.

We should probably rename Animation to SpringAnimator or something.

Animation broken on UIViewControllerAnimatedTransitioning

Hi jtrivedi,
Thanks for sharing. I find some trouble when using Wave with UIViewControllerAnimatedTransitioning
This code works fine with UIView.animation.

import UIKit
import Wave

class PopupViewController: UIViewController {
    lazy var contentView: UIView = {
        let view = UIView()
        view.backgroundColor = .white
        view.layer.cornerRadius = 10
        view.layer.masksToBounds = true
        view.alpha = 0
        view.transform = .init(scaleX: 0, y: 0)
        return view
    }()

    lazy var blurEffectView: UIVisualEffectView = {
        let blurEffect = UIBlurEffect(style: .dark)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = view.bounds
        blurEffectView.alpha = 0.0
        return blurEffectView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
    }

    @objc func viewTapped() {
        dismiss(animated: true)
    }

    private func setupViews() {
        view.backgroundColor = .clear

        blurEffectView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewTapped)))

        view.addSubview(blurEffectView)

        view.addSubview(contentView)
        contentView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.leading.equalToSuperview().offset(20)
            make.trailing.equalToSuperview().offset(-20)
            make.height.equalTo(400)
        }
    }
}

final class TransitionManager:
    NSObject,
    UIViewControllerTransitioningDelegate,
    UIViewControllerAnimatedTransitioning
{
    private let transitionDuration: TimeInterval = 0.6

    let interactiveTransition = UIPercentDrivenInteractiveTransition()

    func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
        transitionDuration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        if let toViewController = transitionContext.viewController(forKey: .to) as? PopupViewController {
            containerView.addSubview(toViewController.view)
            toViewController.contentView.center.y -= containerView.bounds.size.height
            
            let animatedSpring = Spring(dampingRatio: 0.68, response: transitionDuration)
            Wave.animate(withSpring: animatedSpring) {
                toViewController.blurEffectView.alpha = 1
                toViewController.contentView.alpha = 1
                toViewController.contentView.transform = .identity
                toViewController.contentView.center.y += containerView.bounds.size.height
            } completion: { _,_  in
                transitionContext.completeTransition(true)
            }

        } else if let fromViewController = transitionContext.viewController(forKey: .from) as? PopupViewController {
            containerView.addSubview(fromViewController.view)
            let animatedSpring = Spring(dampingRatio: 0.68, response: transitionDuration)
            Wave.animate(withSpring: animatedSpring) {
                fromViewController.blurEffectView.alpha = 0
                fromViewController.contentView.center.y -= containerView.bounds.size.height
                fromViewController.contentView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
            } completion: { _,_  in
                transitionContext.completeTransition(true)
            }
        }
    }

    func animationController(forPresented _: UIViewController, presenting _: UIViewController, source _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self
    }

    func animationController(forDismissed _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self
    }
}
class ViewController: UIViewController {
    ...
    ...
    private let transition = TransitionManager()
    let viewController = PopupViewController()
    viewController.modalPresentationStyle = .custom
    viewController.transitioningDelegate = transition
    present(viewController, animated: true, completion: nil)
    ...
    ...
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.