gontovnik/DGElasticPullToRefresh
Elastic pull to refresh for iOS developed in Swift
Stale — last commit 4y ago
worst of 4 axeslast commit was 4y ago; no tests detected…
no tests detected; no CI workflows detected…
Documented and popular — useful reference codebase to read through.
last commit was 4y ago; no CI workflows detected
- ✓13 active contributors
- ✓MIT licensed
- ⚠Stale — last commit 4y ago
Show 3 more →Show less
- ⚠Concentrated ownership — top contributor handles 53% of recent commits
- ⚠No CI workflows detected
- ⚠No test directory detected
What would change the summary?
- →Use as dependency Mixed → Healthy if: 1 commit in the last 365 days; add a test suite
- →Fork & modify Mixed → Healthy if: add a test suite
- →Deploy as-is Mixed → Healthy if: 1 commit in the last 180 days
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 "Great to learn from" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/gontovnik/dgelasticpulltorefresh)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/gontovnik/dgelasticpulltorefresh on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: gontovnik/DGElasticPullToRefresh
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/gontovnik/DGElasticPullToRefresh 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
WAIT — Stale — last commit 4y ago
- 13 active contributors
- MIT licensed
- ⚠ Stale — last commit 4y ago
- ⚠ Concentrated ownership — top contributor handles 53% of recent commits
- ⚠ No CI workflows detected
- ⚠ No test directory detected
<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 gontovnik/DGElasticPullToRefresh
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/gontovnik/DGElasticPullToRefresh.
What it runs against: a local clone of gontovnik/DGElasticPullToRefresh — 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 gontovnik/DGElasticPullToRefresh | 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 ≤ 1469 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of gontovnik/DGElasticPullToRefresh. If you don't
# have one yet, run these first:
#
# git clone https://github.com/gontovnik/DGElasticPullToRefresh.git
# cd DGElasticPullToRefresh
#
# 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 gontovnik/DGElasticPullToRefresh and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "gontovnik/DGElasticPullToRefresh(\\.git)?\\b" \\
&& ok "origin remote is gontovnik/DGElasticPullToRefresh" \\
|| miss "origin remote is not gontovnik/DGElasticPullToRefresh (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 "DGElasticPullToRefresh/DGElasticPullToRefreshView.swift" \\
&& ok "DGElasticPullToRefresh/DGElasticPullToRefreshView.swift" \\
|| miss "missing critical file: DGElasticPullToRefresh/DGElasticPullToRefreshView.swift"
test -f "DGElasticPullToRefresh/DGElasticPullToRefreshLoadingView.swift" \\
&& ok "DGElasticPullToRefresh/DGElasticPullToRefreshLoadingView.swift" \\
|| miss "missing critical file: DGElasticPullToRefresh/DGElasticPullToRefreshLoadingView.swift"
test -f "DGElasticPullToRefresh/DGElasticPullToRefreshLoadingViewCircle.swift" \\
&& ok "DGElasticPullToRefresh/DGElasticPullToRefreshLoadingViewCircle.swift" \\
|| miss "missing critical file: DGElasticPullToRefresh/DGElasticPullToRefreshLoadingViewCircle.swift"
test -f "DGElasticPullToRefresh/DGElasticPullToRefreshConstants.swift" \\
&& ok "DGElasticPullToRefresh/DGElasticPullToRefreshConstants.swift" \\
|| miss "missing critical file: DGElasticPullToRefresh/DGElasticPullToRefreshConstants.swift"
test -f "DGElasticPullToRefresh/DGElasticPullToRefreshExtensions.swift" \\
&& ok "DGElasticPullToRefresh/DGElasticPullToRefreshExtensions.swift" \\
|| miss "missing critical file: DGElasticPullToRefresh/DGElasticPullToRefreshExtensions.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 1469 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1439d)"
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/gontovnik/DGElasticPullToRefresh"
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
DGElasticPullToRefresh is a Swift UIScrollView/UITableView extension that renders an elastic, bouncy pull-to-refresh control inspired by Hoang Nguyen's Dribbble design. It provides smooth, physics-based animations where the refresh indicator deforms elastically as the user drags, then snaps back when released, with a customizable loading view system. Simple flat structure: the core logic lives in DGElasticPullToRefresh/ directory with five Swift files—Constants.swift for tuning parameters, Extensions.swift for UIScrollView category methods, DGElasticPullToRefreshView.swift as the main gesture handler, DGElasticPullToRefreshLoadingView.swift as the abstract base class for loaders, and DGElasticPullToRefreshLoadingViewCircle.swift as a built-in circle animator. The example app is self-contained in DGElasticPullToRefreshExample/ with ViewController.swift demonstrating usage.
👥Who it's for
iOS developers building Swift apps who want to replace the standard UIRefreshControl with a visually polished, interactive pull-to-refresh gesture. Specifically those targeting iOS 8.0+ and willing to implement custom loading views for branded user experiences.
🌱Maturity & risk
Moderately mature but aging: the codebase targets Swift 3.0 (per .swift-version), supports iOS 8.0+, and includes a working example project. However, there is no visible test suite, no CI/CD setup in the file list, and the repo shows no recent activity indicators in the provided data, suggesting it may be in maintenance mode rather than actively developed.
Single-maintainer risk is high (owned by gontovnik with no evident collaborators in commits). The Swift 3.0 requirement is outdated (modern Swift is 5.x+), which could cause compilation issues in current Xcode versions. No automated tests or CI pipeline means regressions could slip in; manual testing is required for each iOS/Swift version bump.
Active areas of work
No active development visible in the file data provided. The repo appears to be in a stable, maintained state with no mention of open PRs, pending issues, or recent commits. The example project was last touched by multiple users (gontovnik, haawa799, danil.gontovik) but no timestamped activity is evident.
🚀Get running
Clone the repo, open the Xcode project, and run: git clone https://github.com/gontovnik/DGElasticPullToRefresh.git && cd DGElasticPullToRefresh && open DGElasticPullToRefreshExample.xcodeproj. Select the DGElasticPullToRefreshExample scheme and press Cmd+R to build and run the demo on a simulator.
Daily commands:
Open DGElasticPullToRefreshExample.xcodeproj in Xcode 7+, select the DGElasticPullToRefreshExample target, and press Cmd+R. The example app is a bare ViewController with a UITableView that demonstrates the pull-to-refresh on iOS 8.0+. Alternatively, pod install into your own project after adding pod 'DGElasticPullToRefresh' to your Podfile.
🗺️Map of the codebase
DGElasticPullToRefresh/DGElasticPullToRefreshView.swift— Main UIScrollView extension that integrates the pull-to-refresh mechanism into any scrollable view; all contributors must understand the scroll event handling hereDGElasticPullToRefresh/DGElasticPullToRefreshLoadingView.swift— Base loading view class that manages the elastic animation state and visual presentation; required reading for understanding the refresh animation lifecycleDGElasticPullToRefresh/DGElasticPullToRefreshLoadingViewCircle.swift— Default circular loading view implementation with elastic bounce animation; demonstrates the core animation pattern used throughout the libraryDGElasticPullToRefresh/DGElasticPullToRefreshConstants.swift— Centralized constants for animation timing, threshold values, and colors; critical reference for consistent animation behaviorDGElasticPullToRefresh/DGElasticPullToRefreshExtensions.swift— Helper extensions for geometry and animation utilities; foundational for all elastic calculations and view transformationsDGElasticPullToRefreshExample/ViewController.swift— Example integration showing how to attach the pull-to-refresh view to a table view and respond to refresh events
🧩Components & responsibilities
- DGElasticPullToRefreshView (UIView, CALayer, UIGestureRecognizer) — Master control view managing scroll tracking, elastic deformation calculations, and state transitions; bridges UIScrollView events to loading view updates
- Failure mode: If scroll tracking breaks, elastic animation freezes at last position; if deformation math fails, animation appears rigid
- DGElasticPullToRefreshLoadingView (UIView, CAShapeLayer, CABasicAnimation) — Abstract base class defining animation lifecycle (startAnimation, stopAnimation) and percentage-based update hook (updateViewWith); subclasses implement visual presentation
- Failure mode: If animation loop stops, spinner halts mid-rotation; if update hook fails, elastic effect breaks during pull
- DGElasticPullToRefreshLoadingViewCircle (CAShapeLayer, CGPath, CABasicAnimation with easing) — Concrete circular loading indicator with elastic bounce and rotation animation; default implementation used in examples
- Failure mode: If circle path rendering fails, shape becomes invisible; if rotation animation breaks, appears as static circle
- UIScrollView Extension (Swift extensions, objc_setAssociatedObject for property attachment) — Category methods (addDGElasticPullToRefreshViewController, removeDGElasticPullToRefreshViewController) managing lifecycle of pull-to-refresh view attachment and callback routing
- Failure mode: If extension fails to attach, scroll view remains undecorated;
🛠️How to make changes
Add pull-to-refresh to a UITableViewController
- Import the library at the top of your view controller (
DGElasticPullToRefreshExample/ViewController.swift) - Create and configure a DGElasticPullToRefreshLoadingViewCircle (or custom subclass) with desired tintColor (
DGElasticPullToRefreshExample/ViewController.swift) - Call addDGElasticPullToRefreshViewController extension method on your UIScrollView/UITableView, passing the loading view and a refresh handler closure (
DGElasticPullToRefresh/DGElasticPullToRefreshExtensions.swift) - In the refresh handler, perform your data fetch and call removeDGElasticPullToRefreshViewController when complete (
DGElasticPullToRefreshExample/ViewController.swift)
Create a custom loading view animation
- Subclass DGElasticPullToRefreshLoadingView (
DGElasticPullToRefresh/DGElasticPullToRefreshLoadingView.swift) - Override setupViewHierarchy() to create your custom layer hierarchy using CAShapeLayer or CABasicAnimation (
DGElasticPullToRefresh/DGElasticPullToRefreshLoadingViewCircle.swift) - Override updateViewWith(percent:) to animate your layers based on pull distance percentage (0.0 to 1.0+) (
DGElasticPullToRefresh/DGElasticPullToRefreshLoadingView.swift) - Override startAnimation() and stopAnimation() to manage animation state transitions (
DGElasticPullToRefresh/DGElasticPullToRefreshLoadingView.swift)
Customize animation parameters globally
- Edit DGElasticPullToRefreshConstants.swift to adjust animation timing values like kDGElasticPullToRefreshLoadingViewSizeLength (
DGElasticPullToRefresh/DGElasticPullToRefreshConstants.swift) - Modify thresholds like kDGElasticPullToRefreshTriggerDistance (default 80pt) to change when refresh is triggered (
DGElasticPullToRefresh/DGElasticPullToRefreshConstants.swift) - Update animation duration constants like kDGElasticPullToRefreshLoadingViewScale to change the speed of animations (
DGElasticPullToRefresh/DGElasticPullToRefreshConstants.swift) - Recompile and the changes apply globally to all instances using the default constants (
DGElasticPullToRefresh/DGElasticPullToRefreshConstants.swift)
🔧Why these technologies
- Swift & UIKit — Native iOS framework integration; enables seamless adoption in existing UIScrollView/UITableView hierarchies without external dependencies
- CAShapeLayer & CABasicAnimation — Core Animation framework provides efficient, GPU-accelerated animations for elastic deformations and smooth visual effects at 60fps
- UIScrollView extension pattern — Non-invasive integration without requiring view controller subclassing; applies to any scroll view (table, collection, scroll)
⚖️Trade-offs already made
-
Single monolithic DGElasticPullToRefreshView for all scroll tracking and elasticity calculations
- Why: Simpler API surface and fewer inter-component dependencies
- Consequence: Harder to customize individual elastic parameters without subclassing the entire view
-
Animation state managed via simple start/stop lifecycle rather than state machine
- Why: Straightforward implementation matching UIRefreshControl conventions
- Consequence: Less flexibility for complex multi-state loading animations; must override entire updateViewWith() method for custom behavior
-
Global constants file rather than per-instance configuration objects
- Why: Minimal memory overhead and simple approach for standard use cases
- Consequence: All instances share the same animation parameters; customization requires either constants modification or subclassing
🚫Non-goals (don't propose these)
- Does not provide authentication or API communication; refresh handler is user-implemented
- Not a real-time data synchronization framework; one-shot pull-to-refresh only
- Does not handle persistence or caching; purely a UI interaction component
- Does not support horizontal scrolling or custom scroll directions
🪤Traps & gotchas
Swift version mismatch: The .swift-version file specifies Swift 3.0; modern Xcode (14+) may require source migration to Swift 5.x syntax. Manual cleanup required: The README explicitly warns you must call dg_removePullToRefresh() in deinit, or the gesture recognizer and animations may leak. No tests: Changes carry regression risk; you must manually test on multiple iOS versions and simulator/device combos. CADisplayLink tied to scroll: If the scroll view pauses (e.g., user holds finger), animation may stutter; not a trap per se but affects perceived quality.
🏗️Architecture
💡Concepts to learn
- CADisplayLink — DGElasticPullToRefreshView uses CADisplayLink to synchronize animation frames with screen refresh rate; understanding this is critical to tuning animation smoothness and avoiding jank
- Bezier curve path deformation — The elastic effect is achieved by morphing a bezier path as the user drags; the library does not use springs or physics engines but hand-crafted cubic bezier adjustments for performance
- UIScrollView delegate and contentOffset tracking — The entire gesture detection relies on scrollViewDidScroll(_:) and monitoring contentOffset.y; this is the mechanism that fires pull progress updates and must be understood for customization
- CABasicAnimation and CAShapeLayer — The loading view uses CABasicAnimation to rotate the circle and CAShapeLayer for drawing; these Core Animation primitives are foundational for smooth, GPU-accelerated graphics
- Swift extensions and protocol-oriented design — The library uses category extensions (dg_* methods) on UIScrollView and an abstract LoadingView protocol; this pattern is old-school Objective-C style but common in UIKit-era Swift libraries
- Weak reference cycles and deinit cleanup — The README warns about manual dg_removePullToRefresh() because gesture recognizers and animation closures can create retain cycles; understanding memory management in UIKit is essential here
🔗Related repos
ramotion/pull-to-refresh— Alternative pull-to-refresh library with multiple animation styles (line, circle); Ramotion's version is more actively maintained and feature-richjatindersnam/SwiftRefresh— Another Swift UIScrollView extension for pull-to-refresh; shows competing design patterns for custom gesture handlingrealm/realm-swift— Common companion in real iOS apps; DGElasticPullToRefresh often wraps Realm syncs, so understanding Realm's threading model helps integrate this library safelyAlamofire/Alamofire— Typical networking library paired with pull-to-refresh in production; refresh handlers usually dispatch Alamofire requests inside the action closure
🪄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 Swift Package Manager (SPM) support to DGElasticPullToRefresh.podspec
The repo currently only supports CocoaPods installation via the .podspec file. Swift Package Manager is now the standard dependency manager for Swift packages and iOS development. Adding SPM support would make the library more accessible to modern Swift projects and reduce friction for new contributors.
- [ ] Create a Package.swift manifest file in the root directory with proper package definition pointing to DGElasticPullToRefresh/
- [ ] Define the package products, targets, and dependencies in Package.swift following Swift 5.5+ package format
- [ ] Update README.md with SPM installation instructions using .package(url:, from:) syntax
- [ ] Test SPM integration by adding the package to a sample Xcode project via 'Add Packages' dialog
Add unit and integration tests for DGElasticPullToRefreshView animation behavior
The repo has no visible test suite. The core animation logic in DGElasticPullToRefreshView.swift and DGElasticPullToRefreshLoadingView.swift would benefit from tests to prevent regressions during future Swift version updates or refactoring.
- [ ] Create Tests/ directory with DGElasticPullToRefreshViewTests.swift
- [ ] Add unit tests for scroll view delegate methods in DGElasticPullToRefreshView (scrollViewDidScroll, scrollViewDidEndDragging)
- [ ] Add snapshot tests using a library like SnapshotTesting to verify animation frame outputs
- [ ] Integrate tests into an Xcode Test target and verify they pass with current codebase
Modernize Swift syntax and update .swift-version from Swift 3.0 to Swift 5.5+
The project targets Swift 3.0 (marked in .swift-version file), which is now 8+ years old. Modern Swift introduces better optional handling, property wrappers, and performance improvements. Updating to Swift 5.5+ would improve code maintainability and ensure compatibility with current Xcode tooling.
- [ ] Update .swift-version file to 5.5 or later
- [ ] Refactor DGElasticPullToRefresh/*.swift files to use modern Swift syntax (String interpolation, optional chaining improvements)
- [ ] Update DGElasticPullToRefresh.podspec to reflect minimum Swift version s.swift_version = '5.5'
- [ ] Update README.md Requirements section to reflect Swift 5.5+ and test against latest Xcode version
🌿Good first issues
- Add unit tests for DGElasticPullToRefreshConstants: Create a test file that validates threshold values and animation durations to prevent accidental breakage during future tuning. Currently there are zero tests in the repo.
- Document the custom loading view protocol: Add a complete example subclass file in DGElasticPullToRefreshExample/ showing how to implement a waveform or image-based loader beyond the circle example, with in-code comments explaining setPullProgress behavior.
- Modernize Swift version to 5.0+: Update .swift-version, run swift-migrate on the source, and verify compatibility with Xcode 13+. This unblocks adoption on newer projects.
⭐Top contributors
Click to expand
Top contributors
- [@Danil Gontovnik](https://github.com/Danil Gontovnik) — 25 commits
- @gontovnik — 8 commits
- @haawa799 — 2 commits
- @ReadmeCritic — 2 commits
- @benrudhart — 2 commits
📝Recent commits
Click to expand
Recent commits
c525935— Readme change (Danil Gontovnik)833b9d5— Release 1.1 (Danil Gontovnik)8d742ff— Merge branch 'sammygutierrez-swift2' (Danil Gontovnik)0e30f7b— Updated for Xcode 8 beta 6 (Sammy Gutierrez)613ba12— Update DGElasticPullToRefresh.podspec (gontovnik)76faba1— Merge pull request #38 from lfarah/patch-1 (gontovnik)5d5472a— Merge pull request #40 from danielchristopher1/patch-1 (gontovnik)7f30b39— Fixed typo with deinit (danielchristopher1)a417ef9— fixed quotation marks on CocoaPods (lfarah)188a2ff— Merge pull request #30 from shinoys222/master (gontovnik)
🔒Security observations
This is a lightweight UI component library with relatively low security risk. The primary concern is the inclusion of Xcode user-specific files in version control, which could leak developer environment information. No hardcoded secrets, injection vulnerabilities, or critical misconfigurations were detected in the file structure. The codebase appears to be a simple Swift UI component without complex dependencies or external integrations. Recommend adding proper .gitignore rules and completing documentation.
- Medium · User Data in Xcode Project Files —
DGElasticPullToRefreshExample.xcodeproj/xcuserdata/ and DGElasticPullToRefreshExample.xcodeproj/project.xcworkspace/xcuserdata/. The repository contains multiple xcuserdata directories and UserInterfaceState.xcuserstate files from different developers (gontovnik, haawa799, danil.gontovik). These files should not be committed to version control as they may contain sensitive local development settings, breakpoints, or personal configuration data. Fix: Add xcuserdata/ directories to .gitignore. Remove committed user data files from git history using git-filter-branch or similar tools. Ensure .gitignore includes: *.xcuserstate, xcuserdata/, project.xcworkspace/xcuserdata/ - Low · Incomplete README Documentation —
README.md. The README.md file appears to be incomplete (ends mid-word at 'Add DGElast'), which may indicate incomplete security documentation or missing setup instructions that could lead to insecure configurations. Fix: Complete the README.md file with full installation instructions, security considerations, and usage guidelines. Ensure all documentation is properly reviewed before publication.
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.