willowtreeapps/spruce-ios
Swift library for choreographing animations on the screen.
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓7 active contributors
- ✓MIT licensed
- ✓CI configured
Show 3 more →Show less
- ✓Tests present
- ⚠Stale — last commit 2y ago
- ⚠Concentrated ownership — top contributor handles 71% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/willowtreeapps/spruce-ios)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/willowtreeapps/spruce-ios on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: willowtreeapps/spruce-ios
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/willowtreeapps/spruce-ios shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- 7 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Stale — last commit 2y ago
- ⚠ Concentrated ownership — top contributor handles 71% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live willowtreeapps/spruce-ios
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/willowtreeapps/spruce-ios.
What it runs against: a local clone of willowtreeapps/spruce-ios — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in willowtreeapps/spruce-ios | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 813 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of willowtreeapps/spruce-ios. If you don't
# have one yet, run these first:
#
# git clone https://github.com/willowtreeapps/spruce-ios.git
# cd spruce-ios
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of willowtreeapps/spruce-ios and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "willowtreeapps/spruce-ios(\\.git)?\\b" \\
&& ok "origin remote is willowtreeapps/spruce-ios" \\
|| miss "origin remote is not willowtreeapps/spruce-ios (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "Spruce/Spruce.swift" \\
&& ok "Spruce/Spruce.swift" \\
|| miss "missing critical file: Spruce/Spruce.swift"
test -f "Spruce/CoreAnimation/CABasicAnimation+Spruce.swift" \\
&& ok "Spruce/CoreAnimation/CABasicAnimation+Spruce.swift" \\
|| miss "missing critical file: Spruce/CoreAnimation/CABasicAnimation+Spruce.swift"
test -f "Spruce/SortFunction/SortFunction.swift" \\
&& ok "Spruce/SortFunction/SortFunction.swift" \\
|| miss "missing critical file: Spruce/SortFunction/SortFunction.swift"
test -f "Spruce/Animation/Animation.swift" \\
&& ok "Spruce/Animation/Animation.swift" \\
|| miss "missing critical file: Spruce/Animation/Animation.swift"
test -f "Spruce/UIView+Spruce.swift" \\
&& ok "Spruce/UIView+Spruce.swift" \\
|| miss "missing critical file: Spruce/UIView+Spruce.swift"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 813 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~783d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/willowtreeapps/spruce-ios"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Spruce is a Swift library for choreographing complex multi-view animations on iOS with precise timing control. It allows developers to specify animation sequences (like fadeIn, expand, translate) that execute across multiple UIViews with staggered timing and spatial ordering, solving the problem of coordinating polished multi-element animations without hardcoding timing offsets. Single-package structure: core animation logic lives in the main Spruce framework (under implicit Spruce/ source directory based on podspec), while Example/ contains a full CocoaPods-managed iOS example app (Spruce-Example.xcodeproj) demonstrating all animation patterns. UIView extensions (mentioned in README) form the public API surface.
👥Who it's for
iOS developers and designers building production apps who need to execute complex, choreographed animations (e.g., list item entrances, card reveals, sequential view cascades) without managing individual view animation timing, and who want to preserve animations specs from design tools as executable code.
🌱Maturity & risk
Production-ready. The project has v1.0.0+ releases (visible in CocoaPods badge), CircleCI continuous integration, Coveralls code coverage tracking, and Carthage/CocoaPods distribution support. The repo shows active maintenance with organized examples, test infrastructure (.slather.yml for coverage), and contribution guidelines (Contributing.md, Dangerfile for CI).
Low risk for active users but watch for brittleness: the repository is predominantly Swift (264K lines) with minimal dependencies (only build/test infrastructure visible), but commit recency is unclear from the file list. The single-organization maintainer (willowtreeapps) and cross-platform Android counterpart suggest stability, but animation libraries can break with iOS version changes (CABasicAnimation behavior shifts). No visible major open issues listed in the snapshot.
Active areas of work
Repository appears stable rather than actively evolving. No visible PR queue or breaking changes in the file list; CHANGELOG.md exists suggesting semantic versioning discipline. The example app structure and CI configuration (CircleCI, Coveralls) indicate mature maintenance mode rather than active feature development.
🚀Get running
git clone https://github.com/willowtreeapps/spruce-ios.git && cd spruce-ios/Example && pod install && open Spruce-Example.xcworkspace
Daily commands: Open Example/Spruce-Example.xcworkspace in Xcode (not .xcodeproj—CocoaPods workspace required). Build and run Spruce_Example scheme on simulator or device. Run tests via Xcode test navigator (Product > Test) or via CI scripts referenced in Dangerfile.
🗺️Map of the codebase
Spruce/Spruce.swift— Primary entry point and orchestration engine for the animation library; all choreography logic flows through this core classSpruce/CoreAnimation/CABasicAnimation+Spruce.swift— Extension binding Core Animation to Spruce's animation model; essential for understanding how animations are constructedSpruce/SortFunction/SortFunction.swift— Abstract protocol defining how views are sorted for sequential animation timing; fundamental to Spruce's choreography strategySpruce/Animation/Animation.swift— Core animation protocol and configuration model; defines the contract all animation types must implementSpruce/UIView+Spruce.swift— UIView extension adding animation methods; the public API surface developers use to apply Spruce animationsExample/Spruce/ExampleViewController.swift— Primary example demonstrating real-world usage patterns, sort functions, and animation configurations
🧩Components & responsibilities
- Spruce (main orchestrator) (Swift, Core Animation) — Receives animated views and sort function; computes delays, creates CABasicAnimation objects, and applies to layers
- Failure mode: Incorrect delay calculation → animations play out-of-order or overlap unexpectedly
- SortFunction (strategy) (Swift protocols, CGGeometry) — Determines order and timing offset for each view; implementations include linear row-major, radial distance, or custom logic
- Failure mode: Poor sort computation → animation feels uncoordinated or visually jarring
- Animation (model) (Swift, CABasic) — Encapsulates animation parameters (duration, delay, easing); generates CABasicAnimation for a specific view
🛠️How to make changes
Add a New Sort Function
- Create a new struct conforming to SortFunction protocol in Spruce/SortFunction/ (
Spruce/SortFunction/SortFunction.swift) - Implement the required method: func sort(views: [UIView], animationReady: @escaping ([UIView]) -> Void) (
Spruce/SortFunction/SortFunction.swift) - Add a factory method to SortFunctionFactory for convenient instantiation (
Spruce/SortFunction/SortFunctionFactory.swift) - Test with example app by passing your sort function to Spruce.animate(with:using:) (
Example/Spruce/ExampleViewController.swift)
Add a New Animation Type
- Create a new struct conforming to Animation protocol (
Spruce/Animation/Animation.swift) - Implement required properties: duration, delay, timingFunction (
Spruce/Animation/Animation.swift) - Implement the animate(view:) method returning a CAAnimation (
Spruce/CoreAnimation/CABasicAnimation+Spruce.swift) - Use CABasicAnimation extension to configure keyPath and values (
Spruce/Animation/AnimationStandard.swift)
Animate a Table or Collection View
- In your view controller, get a reference to your UITableView or UICollectionView (
Example/Spruce/Table View Example/SpruceTableViewControllerExample.swift) - Create an Animation using AnimationStandard (e.g., fade, scale, translate) (
Spruce/Animation/AnimationStandard.swift) - Choose a SortFunction from SortFunctionFactory (e.g., LinearSort, RadialSort) (
Spruce/SortFunction/SortFunctionFactory.swift) - Call tableView.animate(with:using:) or collectionView.animate(with:using:) to orchestrate animations (
Spruce/UITableView+Spruce.swift)
🔧Why these technologies
- Core Animation (CABasicAnimation, CAAnimationGroup) — Native iOS framework provides GPU-accelerated, performant animations with fine-grained timing control; eliminates need for custom animation loops
- Protocol-oriented design (SortFunction, Animation) — Enables extensibility for custom sort strategies and animation types without modifying core library; leverages Swift's type safety
- UIView/UITableView/UICollectionView extensions — Provides ergonomic API surface by extending standard UIKit classes; eliminates need for custom wrapper views or managers
- Swift Package + CocoaPods/Carthage — Multimodal distribution allows integration into diverse project setups; ensures broad compatibility across iOS development workflows
⚖️Trade-offs already made
-
Choreography via delayed CAAnimation.beginTime rather than sequential apply()
- Why: Allows all animations to start in parallel on the run loop, reducing overhead and enabling smooth 60fps playback
- Consequence: Requires precise calculation of cumulative delays per sort function; any rounding errors compound across large view sets
-
Sort functions computed eagerly before animation starts
- Why: Simplifies timing calculation and avoids dynamic reordering during animation playback
- Consequence: Cannot adapt sort order if view hierarchy changes mid-animation; requires full restart for hierarchy modifications
-
Library agnostic to specific animation content (uses CABasicAnimation properties, not custom renders)
- Why: Minimizes memory footprint and CPU usage; leverages GPU through Core Animation
- Consequence: Complex animations requiring frame-by-frame control must be pre-rendered as video or use custom CADisplayLink solutions outside Spruce
🚫Non-goals (don't propose these)
- Does not handle animation interruption or gesture-driven cancellation (library applies animations fire-and-forget)
- Does not provide physics-based animations (spring, gravity) — limited to timing curves
- Does not animate non-CALayer properties (custom view properties require manual binding)
- Does not support cross-view morphing or shape interpolation
🪤Traps & gotchas
CocoaPods workspace (.xcworkspace) is required—opening .xcodeproj directly will fail to find the Spruce framework. Ruby environment must be configured (.rbenv-version present); build scripts may fail without matching Ruby version. Animation extensions use UIView directly—only available on UIKit (SwiftUI not supported). The library assumes views are already in the view hierarchy before animation; unexpected behavior if views are added/removed during animation execution.
🏗️Architecture
💡Concepts to learn
- CABasicAnimation — Foundation of Spruce's animation execution; understanding Core Animation framework is essential to modify animation behavior or debug timing issues
- Animation Choreography / Staggering — Core pattern Spruce solves—sequencing multiple animations with precise delays and ordering; critical to understanding why this library exists
- UIView Extension Pattern — Spruce exposes its API via
yourView.spruce.prepare()andyourView.spruce.animate()extensions; understanding Swift's extension system is needed to navigate and extend the library - CocoaPods Podspec — The .podspec.json defines exactly what code is exposed, what dependencies exist, and Swift version constraints; critical for dependency management and troubleshooting installation
- View Hierarchy Preparation — Spruce requires calling
prepare()beforeanimate()to set initial state (alpha, scale); understanding iOS view state management prevents animation logic errors - CircleCI + Coveralls Integration — Project uses this CI/code coverage stack; understanding these tools helps contributors run tests locally and pass automated checks
- Timing Function Curves (Easing) — Animations use timing functions to control acceleration/deceleration; likely customizable in Spruce and essential to creating polished motion
🔗Related repos
realm/realm-swift— Comparable production-ready Swift library with robust CI/CD and multi-distribution support (CocoaPods, Carthage, SPM)Alamofire/Alamofire— Mature, well-maintained Swift library with similar distribution model and community contribution patternswillowtreeapps/spruce-android— Official Android counterpart to this library; users wanting cross-platform animation choreography would reference thisninjaprox/NVActivityIndicatorView— Alternative iOS animation library for coordinated view animations; direct competitor in same problem spacelkzhao/Hero— Popular iOS library for view controller transition animations with timing control; overlaps use case for sequential animations
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add unit tests for Spruce animation sorts and layouts
The repo has comprehensive CI/coverage setup (.coveralls.yml, .slather.yml) but the file structure shows no visible Tests directory in the Spruce source folder (only Example/Pods/Target Support Files references). Core animation orchestration logic (sorts, layouts, timing) needs dedicated unit test coverage to prevent regressions as contributors modify animation choreography.
- [ ] Create Tests directory at project root (parallel to Spruce source)
- [ ] Add unit tests for Sort implementations (LinearSort, RadialSort, etc.) with edge cases
- [ ] Add unit tests for Layout implementations (SpriteLayout variants) verifying delay calculations
- [ ] Add integration tests for Animation class verifying sort + layout combinations execute correctly
- [ ] Ensure tests run in CircleCI pipeline (already configured in .circleci)
Document Spruce API and animation customization patterns in CONTRIBUTING.md
CONTRIBUTING.md exists but the README is truncated ('Spruce is a lightweight animation library that helps ch...'). New contributors need clear guidance on how to extend Sorts, Layouts, and Animations. Example/Spruce shows usage patterns but these aren't documented for contributors adding new animation choreography features.
- [ ] Expand CONTRIBUTING.md with 'Architecture Overview' section explaining Sort and Layout abstractions
- [ ] Add code examples showing how to create a custom Sort implementation
- [ ] Add code examples showing how to create a custom Layout implementation
- [ ] Document the animation execution flow and timing model
- [ ] Reference Example/Spruce/Collection View Example as a reference implementation
Modernize Example project for current Swift/iOS versions and add SwiftUI example
Example/Spruce uses XIB files (Base.lproj/LaunchScreen.xib) and UIKit patterns, which is dated for modern iOS development. The Example project is the primary way new contributors understand the library. Adding a SwiftUI example view controller alongside UIKit demonstrates library versatility and helps Swift ecosystem contributors.
- [ ] Create Example/Spruce/SwiftUI Example directory with a SwiftUI View that demonstrates Spruce animations
- [ ] Update Example/Spruce/AppDelegate.swift to show both UIKit and SwiftUI example options
- [ ] Ensure SwiftUI example uses same animation orchestration patterns as Collection View Example for consistency
- [ ] Update example Podfile and .rbenv-version if Swift/Ruby versions are outdated
- [ ] Document SwiftUI integration in README with code snippet
🌿Good first issues
- Add unit tests for edge cases in animation timing (e.g., zero-duration animations, negative delays, views already at target state). Tests likely missing in Spruce_Tests scheme for boundary conditions.
- Document the complete list of supported animation types beyond
[.fadeIn, .expand(.slightly)]with before/after GIFs in README.md; currently only one example shown. - Add Swift Package Manager support by creating Package.swift manifest; currently only CocoaPods and Carthage distribution available, blocking adoption in SPM-only projects.
⭐Top contributors
Click to expand
Top contributors
- @jacksontaylor13 — 71 commits
- [@Erik LaManna](https://github.com/Erik LaManna) — 18 commits
- @zackdotcomputer — 5 commits
- @rafcabezas — 2 commits
- [@Michael Thomas](https://github.com/Michael Thomas) — 2 commits
📝Recent commits
Click to expand
Recent commits
01677e1— Merge pull request #120 from willowtreeapps/bugfix/circle-ci-fix (Erik LaManna)4072285— Update CircleCI YML to new format (Erik LaManna)d161d87— Merge pull request #118 from zackdotcomputer/master (Erik LaManna)0584bfb— Fix compilation of the example project (zackdotcomputer)89d01a3— Run pod install on the example project (zackdotcomputer)c3f84f7— Update Spruce to target iOS 9.0+ (zackdotcomputer)e1d1dca— Fix user-specific link in container (zackdotcomputer)cd29c66— Merge pull request #109 from genuinezack/fix/TimedView-accessibility (Erik LaManna)6d11667— MakeTimedViewinitpublically accessible (zackdotcomputer)c985945— Merge pull request #106 from willowtreeapps/feature/warning-fixes (Erik LaManna)
🔒Security observations
The Spruce iOS animation library codebase shows a reasonable security posture for a UI animation library. The primary concerns are operational rather than code-level vulnerabilities: committed dependency directories (Pods/) and user-specific Xcode configurations that should be gitignored. No hardcoded secrets, injection vulnerabilities, or critical misconfigurations were identified in the visible file structure. The library lacks formal security scanning in its CI/CD pipeline (based on visible config files). As an animation library with no apparent network, database, or sensitive data handling, the security risk profile is inherently lower than applications handling authentication or financial data. Recommendations focus on improving development practices and adding automated security scanning.
- Medium · Pods Directory Committed to Repository —
Example/Pods/, Example/Podfile.lock. The Example/Pods directory and Podfile.lock are committed to version control. This includes third-party dependencies and generated files that should typically be ignored. This increases repository size, makes it harder to track actual source code changes, and can lead to merge conflicts. Fix: Add 'Pods/' and 'Podfile.lock' to .gitignore. Developers should run 'pod install' locally. Consider using a CI/CD pipeline to verify dependency integrity. - Low · User-Specific Xcode Configuration Committed —
Example/Pods/Pods.xcodeproj/xcuserdata/. The .xcuserdatad directory containing user-specific Xcode settings and schemes is committed to the repository. This includes paths and configurations specific to individual developers (e.g., 'jacksontaylor.xcuserdatad'). Fix: Add '*.xcuserdatad' to .gitignore to prevent committing user-specific Xcode configurations. This prevents merge conflicts and keeps the repository clean. - Low · Missing Security Baseline in Configuration —
Repository root configuration. No evidence of security-focused configuration files such as .swiftlint.yml, .pre-commit hooks, or security scanning configurations. The library lacks automated security checks in the build pipeline. Fix: Implement SwiftLint with security rules, add pre-commit hooks for vulnerability scanning, and integrate tools like Sonarqube or danger-swift for automated security checks in CI/CD. - Low · Outdated CocoaPods Version Badge —
README.md. The README shows a CocoaPods badge with version 'v1.0.0' which may be outdated. This could mislead users about the current version and available security updates. Fix: Keep the version badge synchronized with actual releases. Consider using dynamic badges that pull from CocoaPods API to ensure accuracy.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.