Skip to content

jensvansteen/ScrollEdgeBar

Repository files navigation

ScrollEdgeBar

SwiftUI safeAreaBar-based scroll-edge bars, made easy to use from UIKit.


Why This Library Exists

As of iOS 26, there is no public UIKit API that allows custom views to blend with the blur of the navigation bar or tab bar. UIScrollEdgeElementContainerInteraction exists but does not integrate with the system bar blur — it renders its own separate background, resulting in two distinct blur effects that don't fluently merge.

I originally wanted to bring this effect to React Native, but it wasn't even solved at the UIKit level. By investigating SwiftUI's internals, I found that SwiftUI coordinates between the scroll edge bar and the navigation/tab bar without requiring them to be in the same view hierarchy. Unable to recreate this in pure UIKit, I built this wrapper that bridges through SwiftUI to achieve the effect.

Note: The library locates rendered bar frames by walking the view hierarchy. It does not call any private methods or import private frameworks, but it does rely on an internal implementation detail that could theoretically change in a future iOS release. I will actively monitor for changes, and hope Apple exposes a native UIKit API that makes this library unnecessary.

Features

  • Seamless glass blur — extends navigation bar and tab bar Liquid Glass (iOS 26+)
  • Graceful fallback — uses safeAreaInset on iOS 16–25, same layout without the blur
  • Top & bottom bars — attach a bar to either scroll edge, or both
  • UIKit-native — works with any UIScrollView, UITableView, or UICollectionView
  • UIView-based content — pass any UIView as bar content
  • Automatic insets — content insets and scroll indicators managed for you

Requirements

  • iOS 16.0+
  • Swift 6.2+
  • Xcode 26.0+

Installation

Swift Package Manager

Add to Package.swift:

dependencies: [
    .package(url: "https://github.com/jensvansteen/ScrollEdgeBar.git", from: "1.2.0")
]

Or via Xcode: File → Add Package Dependencies and enter the repository URL.

CocoaPods

pod 'ScrollEdgeBar', '~> 1.2'

Usage

Basic Setup

import ScrollEdgeBar

class MyViewController: UIViewController {

    private let tableView = UITableView()
    private var edgeBarController: ScrollEdgeBarController?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Set up your scroll view as usual
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        // ... constraints, dataSource, etc.

        // Wrap it with ScrollEdgeBarController
        let controller = ScrollEdgeBarController(scrollView: tableView)

        let segmented = UISegmentedControl(items: ["First", "Second"])
        segmented.selectedSegmentIndex = 0
        controller.setTopBar(segmented)

        addChild(controller)
        view.addSubview(controller.view)
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            controller.view.topAnchor.constraint(equalTo: view.topAnchor),
            controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
        controller.didMove(toParent: self)
        edgeBarController = controller
    }
}

Top and Bottom Bars

let controller = ScrollEdgeBarController(scrollView: scrollView)

controller.setTopBar(myFilterView)
controller.setBottomBar(myToolbarView)

Removing Bars

controller.removeTopBar()
controller.removeBottomBar()

Opting Out of Glass Effect

By default, the glass blur effect is used on iOS 26+. Pass prefersGlassEffect: false to use plain safeAreaInset bars on all OS versions:

let controller = ScrollEdgeBarController(scrollView: scrollView, prefersGlassEffect: false)

Estimated Heights

Provide estimated bar heights to prevent a brief layout flicker on first appearance:

controller.estimatedTopBarHeight = 48
controller.estimatedBottomBarHeight = 44

Stable Slot Hosts (React Native / Custom Frameworks)

safeAreaBar works best when the UIView identity it hosts remains stable across updates. If you are integrating from a framework like React Native that manages its own view lifecycle and may reparent or replace views, use ScrollEdgeBarSlotHostView as a fixed container:

let slot = ScrollEdgeBarSlotHostView()
controller.setTopBar(slot)

// Later, swap content without touching the bar itself
slot.setContentView(myReactNativeView)

The slot's identity stays fixed in the SwiftUI hierarchy while the content inside can be replaced freely.

API Reference

Member Description
init(scrollView:prefersGlassEffect:) Creates a controller wrapping the given scroll view. prefersGlassEffect defaults to true
scrollView The wrapped scroll view (read-only)
prefersGlassEffect When true (default), uses iOS 26 glass blur if available. Set to false for plain bars on all OS versions
setTopBar(_:) Sets a UIView as the top edge bar
setBottomBar(_:) Sets a UIView as the bottom edge bar
removeTopBar() Removes the top bar
removeBottomBar() Removes the bottom bar
estimatedTopBarHeight Estimated top bar height used before layout (default: 60)
estimatedBottomBarHeight Estimated bottom bar height used before layout (default: 60)

ScrollEdgeBarSlotHostView

Member Description
setContentView(_:) Mounts a view inside the slot, replacing any previous content
contentView The currently mounted view (read-only)

Examples

The Example/ directory contains a full demo app. Open Example/ScrollEdgeBarExampleApp/ScrollEdgeBarExampleApp.xcodeproj in Xcode 26 to run it.

App Store Listing

Segmented control as top bar above a ranked app list.

219_1440x30_shots_so.mp4

App Store (No Glass)

Same screen with prefersGlassEffect: false, showing the plain safeAreaInset bar.

159_1440x30_shots_so.mp4

Pull Requests

Horizontally scrolling filter chips with large title navigation.

849_1440x30_shots_so.mp4

Note: When a safeAreaBar is present alongside a large title navigation bar, SwiftUI applies the scroll edge blur effect to the navigation bar even when the content is at rest, causing it to appear blurry on first appearance. This is a known SwiftUI behavior (FB21613303).

PR Detail

Glass-effect review banner (top) and action buttons (bottom) using UIGlassEffect.

139_1440x30_shots_so.mp4

Transition Showcase

Large colored blocks demonstrating how the glass blur color transitions as you scroll.

46_1440x30_shots_so.mp4

Toolbar

Bottom edge bar positioned above the system UIToolbar.

235_1440x30_shots_so.mp4

Search Bar

UISearchController in the navigation bar with a segmented control edge bar below it.

62_1440x30_shots_so.mp4

Calendar

Week day selector top bar with strong edge blur, simulating the Calendar app. The strength of the blur as content scrolls behind the bar is controlled via UIScrollView.topEdgeEffect:

scrollView.topEdgeEffect.style = .automatic // default
scrollView.topEdgeEffect.style = .soft
scrollView.topEdgeEffect.style = .hard      // strongest, matches Calendar app

// Also available for other edges
scrollView.bottomEdgeEffect.style = .hard

773_1440x30_shots_so.mp4

Author

Created by Jens Van Steen · GitHub

License

MIT License. See LICENSE file.

About

UIKit bridge for SwiftUI's safeAreaBar — embed custom views in the scroll edge of the navigation or tab bar

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors