ScalingHeaderScrollView: SwiftUI Header Collapse and Pull Actions
A scroll view with a sticky header which shrinks as you scroll. Written with SwiftUI.
At a glance
- What is it?
- exyte/ScalingHeaderScrollView is a SwiftUI scroll view wrapper that turns a header into a collapsing, growing, and snapping element. It suits iOS interfaces with long content, but its reliance on scroll position makes it unsuitable for a TabView-based layout.
- Who is it for?
- Adopt ScalingHeaderScrollView for a SwiftUI screen whose header should collapse over vertically scrolling content and whose pull behavior matches the component model. Do not choose it for a TabView with changing tab heights, or for interfaces requiring a stable scroll position across independently scrollable tabs.
- 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?
- Yes. The repository last received commits 3 days ago.
- 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 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What ScalingHeaderScrollView Changes in a SwiftUI Screen
ScalingHeaderScrollView addresses a specific layout problem: a screen needs a large visual header at the top, followed by content that scrolls beneath it, while the header becomes smaller as the user moves through the page. It is intended for SwiftUI developers building iOS interfaces where navigation or visual context should remain represented by a compact header. The component is not a general navigation system or a replacement for every SwiftUI scroll container. Its scope is the relationship between a header view and vertically scrolling content.
The package is written in Swift and distributed as the `exyte/ScalingHeaderScrollView` project. The README identifies the default component name as `ScalingHeaderScrollView`, while one note refers to `ScalingScrollview`; that naming inconsistency is worth checking when integrating the package. The available API also goes beyond basic shrinking. It can expose collapse progress, detect when the bottom is reached, add pull-to-refresh and pull-to-load-more actions, reset the scroll position, and move the header to defined states.
This makes the component most relevant to detail screens, feeds, profiles, and other layouts where one continuous scroll view is acceptable. Its feature set assumes that the application owns the scroll interaction and can coordinate asynchronous work such as refreshing or loading additional content.
How the ScrollView Wrapper Handles Header Motion
The documented implementation is a SwiftUI view built around a `ScrollView`. The header and content are supplied separately through `@ViewBuilder` parameters, allowing the caller to compose any SwiftUI views in each region. The header is not merely placed beside the content. Its visual state changes according to the scroll position, which is why the README describes it as a sticky header that shrinks while scrolling.
The core movement is exposed through `.collapseProgress(_:)`. This method takes a `Binding<CGFloat>` whose value ranges from `0` when the header is not collapsed to `1` when it is fully collapsed. An application can use that value to coordinate other visual changes. The package also supports changing the minimum and maximum header heights with `.height(min:max:)`. The documented defaults are `150.0` for the minimum and `350.0` for the maximum.
The package distinguishes several kinds of movement. By default, scrolling upward moves the header upward rather than collapsing its actual height. `.allowsHeaderCollapse()` enables a real height collapse during upward scrolling. `.allowsHeaderGrowth()` enables header scaling during downward scrolling, but that behavior is disabled by default. These are different policies, so adopting the package does not require every motion effect at once.
Snapping is another part of the mechanism. `.setHeaderSnapMode(_:)` accepts `.disabled`, `.immediately`, and `.afterFinishAccelerating`. Custom snap points can be supplied with `.headerSnappingPositions(snapPositions:)`, using values from `0` through `1`, where `0` means fully collapsed and `1` means fully expanded. The README says snapping is disabled by default.
Installing the Package and Building a First Header
The repository includes `Package.swift`, and the README identifies the project as compatible with Swift Package Manager. The README does not provide a complete installation command, package URL command, or Xcode package operation, so there is no documented command to reproduce here. The README also shows a CocoaPods badge marked deprecated after version `1.1.4`; it does not give a CocoaPods installation command.
The first documented use is to put the header and content into the `ScalingHeaderScrollView` constructor. The following example uses a `ZStack` for the header and a `Text` view for the scrolling content:
struct ContentView: View {
var body: some View {
ScalingHeaderScrollView {
ZStack {
Rectangle()
.fill(.gray.opacity(0.15))
Image("header")
}
} content: {
Text("↓ Pull to refresh ↓")
.multilineTextAlignment(.center)
.padding()
}
}
}The header block is passed first and the content block follows the `content:` label. The README lists `header` and `content` as required `@ViewBuilder` parameters. A project using this example must provide an asset named `header` and place the view in an application target that can resolve the package.
Pull-to-Refresh, Load-More, and Scroll Reset APIs
ScalingHeaderScrollView includes loading actions that are tied to the scroll view rather than implemented by the caller as a generic overlay. Pull-to-refresh is configured with `.pullToRefresh(isActive:perform:)`. The `isActive` value dynamically enables or disables the behavior. The README specifically states that no loading indicator appears when the action is not set up. The `perform` closure is asynchronous, and the loading indicator is hidden after that closure completes.
Load-more uses a similar model. The `.pullToLoadMore(isActive:contentOffset:perform:)` modifier accepts an enablement flag, a `contentOffset` representing padding from the bottom edge while the action is active, and an asynchronous closure. The indicator is also conditional on configuring the action. This design keeps the loading state tied to the scroll component, but it does not remove the need for the application to manage its own data and error state.
The package can notify the caller when the scroll view reaches the bottom with `.scrollViewDidReachBottom(perform:)`. It can also reset the scroll position through `.scrollToTop(resetScroll:)`, where the binding changes to `true`. The exact reset lifecycle after that value changes is not further described in the README.
For a fixed header state, `.snapHeaderToState(state:animated:)` accepts a binding whose state can be `.collapsed`, `.expanded`, or `.custom(CGFloat)`. The documentation uses `CFGloat` in its text, while Swift's type name is normally written `CGFloat`. Integration should confirm the actual public signature in the package source.
The TabView Limitation and a Better Layout Boundary
The clearest limitation is documented directly: `TabView` will not work as intended with this component. The README explains that the component is a `ScrollView` under the hood. When content height changes between tabs inside that scroll view, there is no way to keep the scroll position stable. It states that the usual solution is a separate `ScrollView` for each tab, rather than one scroll view around the tabs.
The reason is structural, not a missing convenience modifier. `TabView` exposes little control over its current position and tab content height. The package relies heavily on the scroll offset, so the component cannot manually correct that state when the contained tab changes its size. A `TabView` inside a `ScrollView` would also have unexpected behavior for the same general reason.
A real alternative is to keep each tab's scroll state independent and place a separate `ScrollView` inside each tab. If each tab also needs a shrinking header, the design must account for those separate scroll regions rather than assuming that one shared `ScalingHeaderScrollView` can coordinate them. This approach accepts less automatic header sharing in exchange for clearer tab transitions and more stable per-tab content.
Another boundary is behavioral. The component is designed around a continuously measured scroll offset and optional async actions. A screen whose main navigation changes identity, resets content unexpectedly, or relies on multiple independent scrolling regions may be a poor fit even if the visual header requirement looks similar.
Maintenance, Packaging, and MIT Licensing
The repository is not archived, and its last push was on 2026-08-17. The repository has no GitHub releases, so there is no documented release cadence in the README. The README includes an SPM compatibility badge and a CocoaPods badge stating that CocoaPods is deprecated after `1.1.4`. That makes Swift Package Manager the clearer packaging path based on the documentation, although the exact package requirements should be checked in `Package.swift`.
The project uses the MIT license. The repository includes a `LICENSE` file, and the README identifies the project as MIT licensed. This is a permissive license category, but the license text should be reviewed for obligations such as preserving copyright and license notices when distributing the code. The license identifier itself does not establish compatibility with a particular application or deployment setup.
Upgrade cost depends on whether an application uses only the basic initializer or relies on the full modifier surface. A basic screen may have a small integration surface. Pull-to-refresh, load-more, snapping, collapse progress, and state binding introduce more lifecycle and state decisions. The README does not document a compatibility matrix, migration guide, release notes, or rollback procedure, so those points cannot be confirmed from the repository documentation.
Editorial conclusion
Adopt ScalingHeaderScrollView for a SwiftUI screen whose header should collapse over vertically scrolling content and whose pull behavior matches the component model. Do not choose it for a TabView with changing tab heights, or for interfaces requiring a stable scroll position across independently scrollable tabs. Verify the package's current platform and Swift compatibility first, then confirm that the required header and content builders produce the intended layout.
Frequently asked questions
How do I install ScalingHeaderScrollView in a SwiftUI project?
The repository includes `Package.swift` and the README marks the project as SPM compatible, but it does not provide a complete installation command or package URL. It also does not give a CocoaPods installation command.
Can ScalingHeaderScrollView be used inside a SwiftUI TabView?
The README says it will not work as expected inside a `TabView`. It recommends placing separate `ScrollView` instances on each tab because changing tab content height can make a shared scroll position unstable.
How does ScalingHeaderScrollView support pull-to-refresh and pull-to-load-more?
Use `.pullToRefresh(isActive:perform:)` or `.pullToLoadMore(isActive:contentOffset:perform:)`. The README states that loading indicators appear only when these actions are configured, and their asynchronous closures control when loading ends.
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/exyte-scalingheaderscrollview)Was this useful?