Spruce: CocoaPods resolves to 2017, Carthage to 2024, and springDampening is permanent
GitHub describes it as Swift library for choreographing animations on the screen.. The repository metadata lists Swift as its primary language. The metadata lists the MIT license. This article stays within the project description and details documented in the GitHub repository README.
At a glance
- What is it?
- willowtreeapps/spruce-ios is a Swift animation library whose two documented package managers resolve to code seven years apart, whose only release is from March 2017, whose build tooling is four Ruby tools and a shell script with no Package.swift, and whose custom animation example animates a view to one and a half times its size.
- Who is it for?
- Vendor it rather than depend on it, and pick your version deliberately, because the two documented install routes do not agree about what version you get: CocoaPods resolves the 1.0 range to the single 2017 release while Carthage takes the 2024 default branch. Two things to check before you write code against it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Probably not. The repository last received commits 31 months ago, on March 18, 2024.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Star history
- willowtreeapps/spruce-ios3,423 ★ · -6
| Repository | 2026-08-13 | 2026-10-22 |
|---|---|---|
| willowtreeapps/spruce-ios | 3.4k | 3.4k |
-6 Stars in the last 70 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The two documented package managers resolve to code seven years apart
The installation section gives you two options and they do not agree on what version you get.
The CocoaPods line pins a version range. Add pod "Spruce", '~> 1.0.0' to your Podfile, and the tilde means anything from 1.0.0 up to but excluding 1.1.0. The only release this repository has ever published is 1.0.0, dated 2017-03-10. So that range has exactly one resolvable version in it, and a CocoaPods consumer gets a build from March 2017.
The Carthage line has no version constraint at all. Add github "willowtreeapps/spruce-ios" to your Cartfile, and Carthage resolves the default branch. The default branch was last committed on 2024-03-18. So a Carthage consumer gets seven-year-newer source than a CocoaPods consumer, from the same README, on the same page.
That gap is not a documentation oversight with no consequence. The two managers differ in how they build too: CocoaPods hands you a compiled framework or source pod, Carthage builds from source itself. So the two documented routes differ in version, in build step, and in what happens when the library has a compile error.
The README also offers a third route with no version handling either: download the pre-built frameworks from the releases page, which contains the same single 1.0.0 from 2017.
If you are integrating this today, the version you actually get is determined by which of two lines you paste, and neither line tells you which one is current.
One release from 2017, a last commit in March 2024, and no Package.swift
There is exactly one release in this repository's history: version 1.0.0, published 2017-03-10.
The last commit to the default branch is dated 2024-03-18. That is seven years after the release, and it is nine hundred and thirty days before the start of October 2026. So the source has moved a long way past the only thing anyone can install by version, and there is no second tag to point at.
The installation section names two package managers, CocoaPods and Carthage. It does not mention Swift Package Manager, and the repository listing has no Package.swift in it. What is there is a Spruce.podspec, which is the CocoaPods specification file, and a Cartfile is not present because a Cartfile belongs in the consuming project rather than the library.
That combination is the thing to weigh before you depend on this. A Swift library whose last commit is two and a half years old, whose only installable release is nine years old, and which ships no manifest for the package manager that Swift libraries are increasingly distributed through, is a library you would vendor rather than depend on.
The README's own maintenance language is careful and should be read as carefully. It says the project currently supports both CocoaPods and Carthage, and that if you want to do everything yourself without a package manager you can checkout the releases page for pre-built frameworks or take the exact source from the project. Nothing in it claims ongoing work, and nothing in it promises a release.
The build tooling is Ruby, in four places, in a Swift library
Count the non-Swift tooling in the repository's top level and there are five entries.
There is a Gemfile and a Gemfile.lock, which is Bundler, for Ruby. There is a .rbenv-version file, which pins which Ruby version the gems are meant to run under. There is a .slather.yml, and Slather is a Ruby tool that turns Xcode coverage data into a browsable HTML report. There is a Dangerfile, and Danger is a Ruby tool that reads a diff during continuous integration and posts review warnings back onto the pull request. And there is a fastlane directory, which is Ruby, used for building and releasing.
Alongside those, a shell script with a typo in its own filename, generateDocumenation.sh, drives the documentation build. And a circle.yml, which is CircleCI's first-generation filename; the second generation expects its configuration under .circleci.
So the pipeline is: CircleCI, Coveralls for coverage through its own config file, Danger for pull request review, Slather for the coverage report, fastlane for releases, and a shell script for documentation. Five tools, four of them Ruby, none of them Swift Package Manager.
There is also a coverage configuration file for Coveralls and a repository entry for the documentation output directory. What there is not, anywhere in the listing, is a Package.swift.
None of this is criticism of the tooling choices. Bundler and fastlane were normal for Swift library distribution at the time. It is simply the shape of the repository: the code is Swift, and everything around the code is Ruby and shell.
Two of the five badges have no URL and one points at the legacy CircleCI domain
The badge row at the top of the README has five entries and every one of them is an empty markdown link, a pair of brackets with nothing between them and a URL after it.
Two of those URLs are empty as well. The raw markup is an empty link target, so the anchor goes nowhere at all rather than to a stale page. That is a different failure from a dead badge: a badge pointing at a retired service tells you the service is gone, and an empty target tells you nothing.
Of the three that have URLs, one points at circle-ci.org, which is CircleCI's older domain rather than the one the service uses now. One points at Coveralls. One points at Carthage's repository. One points at CocoaPods's repository.
So the visible integration story is CircleCI for builds, Coveralls for coverage, and two distribution channels. There is no badge for a package manager the project does not use, and no badge for anything in the repository's fastlane directory.
One more thing in the same region. The section that introduces the library ends with the sentence here is a quick example of how you can Spruce up your screens, and the next line is the installation heading. So the promised quick example is not on the page. Given that the README's images live in a separate directory in the repository, it was most likely an animated capture that did not survive being rendered as text.
The public property springDampening keeps a misspelling that cannot be corrected
One of the two built-in animation classes is SpringAnimation, and its documented job is to use the UIKit animate function that takes spring damping, so that views bounce. Its two tunable properties are named springDampening and initialVelocity.
The UIKit function it calls is animate with arguments including usingSpringWithDamping. Damping, with an a.
So the property is spelled with an e and the framework function it forwards to is spelled with an a. The misspelling is in the library's own public API, which means it is in every call site that sets it, which means changing it would be a source-breaking change for anyone who configured a spring animation.
That is why it is worth writing down rather than filing and forgetting. It is not a typo that can be corrected later; it is a typo that is now load-bearing, in the same way an incorrect parameter name in a command line interface becomes permanent once people have scripts that use it.
The rest of that section is brief and honest. StandardAnimation uses the default UIView animate function and is what you want for stock linear movement. SpringAnimation is the one that bounces. Nothing here says how either interacts with the sort functions, which are described separately as controlling the order in which subviews animate rather than the movement itself.
The custom animation example scales the view to 1.5 and leaves it there
The custom animation walkthrough has you write two closures. The first is a prepare handler, and it shrinks the view:
let prepareHandler = { view in
view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
}The second is a change function, and it does the actual animation:
let changeFunction = { view in
view.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}The prepare handler scales to 0.1. The change function scales to 1.5.
Now read that against the library's own stated contract, given in the basic usage section: prepare sets each view's alpha to zero and shrinks it so that when the animation runs the view will revert to its original position.
One and one is the original scale. One point five is half again as large. So the documented example of how to build a custom animation prepares the view at a tenth of size and animates it to one and a half times its proper size, and the library has no step that returns it to one. The view is left permanently oversized.
That is a small thing in isolation, and it is the kind of thing where a reader might reasonably assume the example is abbreviated. But the example is the only definition of the custom API in the README, it is complete enough to run, and the section around it uses the word definitely when encouraging you to build your own.
If you are copying that snippet, change the 1.5 to 1.0 before you use it.
The Animation protocol has no prepare, yet prepare is the first documented step
The basic usage section teaches a two-step contract. You call prepare on the view with the animations you want, then you call animate with the same animations. Prepare sets the starting state; animate runs the transition.
Then the Basic Concepts section defines the protocol you implement to make a custom animation, and it has two members:
var changeFunction: ChangeFunction? { get set }
func animate(delay: TimeInterval, view: UIView, completion: CompletionHandler?)One property and one function. There is no prepare in the protocol. The prepare side of the contract arrives through the stock animation class instead, whose custom initialiser takes a prepare function and an animate function as arguments and hands them to the object it builds.
So the protocol that defines an animation describes only half of what the documentation says you must do. If you implement the protocol directly, you have a change function and an animate method, and no starting state, which means the view has nothing to transition from unless you set it yourself.
The change function is also declared optional. The prose next to it says it is important that the change function is set before animate is called, and that Spruce should handle all of that. So the type system permits nil, the contract forbids it, and the README does not say what an animation with a nil change function does when it runs.
There is also a documentation link for more on the animate method, and it points at a path containing a slash-separated directory called swift_output, which is the shape of generated Swift documentation output rather than a hand-maintained site.
The documentation deep link's anchor is a compiler-mangled Swift symbol
The basic usage section ends by linking to the documentation for more functions. The link goes to a page under the project's documentation site, and its anchor is not a readable name. It is a run of letters and digits separated by underscores and colons, ending in a sequence that encodes a Swift extension property on UIView.
That is a compiler-generated symbol name. Documentation tools derive those anchors by asking the compiler to mangle the Swift declaration, and the mangling scheme is an implementation detail of the compiler rather than of your code. It has changed between Swift versions before, and when it does, every generated anchor that used the old scheme stops resolving.
So the one deep link in this README points at a fragment whose stability depends on the toolchain that produced the documentation rather than on anything in the library's source. If the docs were regenerated with a different compiler, the link in the README has no mechanism to notice.
This is a small, unglamorous detail, and it is the kind that only shows up when a link has quietly stopped working. It is also avoidable: a documentation site that anchors on the declared name rather than the mangled symbol would survive any future toolchain.
Two other things are worth noting in the same file. The Getting Started section says the library is entirely written in Swift and currently only supports Swift, and that Objective-C wrappers are coming soon. And the sort function section describes around eight implementations, names two of them in prose rather than in a list, and tells you to run the example app to see what the others look like.
Editorial conclusion
Vendor it rather than depend on it, and pick your version deliberately, because the two documented install routes do not agree about what version you get: CocoaPods resolves the 1.0 range to the single 2017 release while Carthage takes the 2024 default branch. Two things to check before you write code against it. The custom animation example in the README animates to one and a half times the view's size and never returns it, so change that constant. And the property named springDampening is misspelled relative to the UIKit function it wraps, which means the misspelling is now part of the public API and cannot be corrected without breaking callers. Skip it if you need a package manager it does not ship a manifest for.
Frequently asked questions
How do I install Spruce for iOS?
Two documented routes. For CocoaPods, add pod "Spruce", '~> 1.0.0' to your Podfile. For Carthage, add github "willowtreeapps/spruce-ios" to your Cartfile. The README also says you can download pre-built frameworks from the releases page, or take the source directly from the repository.
What is the newest Spruce iOS release?
Version 1.0.0, published 2017-03-10, is the only release on record. The last commit to the default branch is dated 2024-03-18, so the source is far ahead of the only installable version.
Does Spruce support Objective-C?
Not according to the Getting Started section, which says Spruce is entirely written in Swift and currently only supports Swift, and that Objective-C wrappers are coming soon. No Objective-C wrapper appears in the repository's top-level listing.
How many SortFunction implementations does Spruce ship?
Around eight, per the SortFunction section, which describes them as implementations with a wide open possibility to make more. It names LinearSortFunction and RadialSortFunction in prose rather than listing them, and suggests running the example app to preview them.
Does Spruce support Swift Package Manager?
The installation section names CocoaPods and Carthage only, plus downloading pre-built frameworks from the releases page. The repository listing has a Spruce.podspec and no Package.swift.
What do prepare and animate do in Spruce?
prepare goes through each view, sets its alpha to 0.0 and shrinks it so the animation appears to start from the view's normal position. animate then runs the animations. The README does not say what happens if prepare is called without a matching animate, or twice.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/willowtreeapps-spruce-ios)Star history chart for your README
[](https://hysenlabs.com/projects/willowtreeapps-spruce-ios)