diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78ce9e9..2413657 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,12 +95,20 @@ jobs: with: xcode-version: ${{ env.XCODE_VERSION }} + - name: Cache DerivedData + if: env.turbo_cache_hit != 1 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/Library/Developer/Xcode/DerivedData + key: ${{ runner.os }}-deriveddata-${{ hashFiles('example/ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-deriveddata- + - name: Install cocoapods - if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true' + if: env.turbo_cache_hit != 1 run: | cd example bundle install - bundle exec pod repo update --verbose bundle exec pod install --project-directory=ios - name: Build example for iOS diff --git a/.gitignore b/.gitignore index 67f3212..ed758cc 100644 --- a/.gitignore +++ b/.gitignore @@ -41,8 +41,10 @@ project.xcworkspace local.properties android.iml +# Example app Android (iOS-only library) +example/android/ + # Cocoapods -# example/ios/Pods # Ruby @@ -55,11 +57,6 @@ npm-debug.log yarn-debug.log yarn-error.log -# BUCK -buck-out/ -\.buckd/ -android/app/libs -android/keystores/debug.keystore # Yarn .yarn/* diff --git a/README.md b/README.md index 781c45e..a8ef36d 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,9 @@ export function Example() { estimatedTopBarHeight={48} estimatedBottomBarHeight={56} > - + @@ -76,7 +78,9 @@ export function Example() { ))} - + Bottom Bar @@ -87,10 +91,7 @@ export function Example() { If your navigation header or tab bar is external to the scroll-edge-bar container, you can provide explicit offsets: ```tsx - + ... ``` @@ -113,15 +114,160 @@ pod install The example app in `example/` currently demonstrates: - a native stack header via React Navigation native stack -- a native bottom tab bar via React Navigation native bottom tabs - a `ScrollEdgeBar.TopBar` with a segmented control - a `ScrollEdgeBar.BottomBar` with a label and switch +## Adaptive Bar Content + +`ScrollEdgeBar` uses the native iOS scroll-edge bar surface, so the bar material/blur itself follows the content underneath the scroll view. + +There is one important distinction between the bar surface and the content you render inside it: + +- Native UIKit/SwiftUI controls participate in the system scroll-edge appearance environment. +- Arbitrary React Native children are hosted as Fabric/RN views inside that native bar surface. + +This means RN-rendered content, such as `Text` or `View` with JS-defined colors, may not automatically adapt its foreground and fill colors during the local scroll-edge transition in the same way native UIKit/SwiftUI controls do. The bar material can transition correctly while RN child colors remain as styled by React Native. + +This behavior is context-sensitive: + +- When a bar merges with an existing system `UINavigationBar` or `UITabBar`, iOS already provides a stronger system scroll-edge context. In those cases, RN child content can appear to integrate better with the system bar’s material transition. +- In a standalone bottom `safeAreaBar`, with no system `UITabBar` to merge into, arbitrary RN children may not receive the same adaptive foreground/fill treatment. +- SwiftUI-backed content does adapt correctly in the standalone bottom bar case. + +This is why the same visual example can behave differently depending on whether it is attached to a navigation bar, attached to a tab bar, or rendered as a standalone bottom safe-area bar. + +We investigated whether the standalone bottom-bar behavior could be fixed by adding hidden native/SwiftUI content internally. In local testing, the following did **not** reproduce the adaptive behavior for RN-rendered labels/buttons: + +- a hidden SwiftUI view inside the bottom safe-area bar +- a layout-participating SwiftUI sibling inside the bar +- a nested `UIHostingController` probe +- visible native SwiftUI controls inserted as UIKit subviews inside the RN bottom-bar marker + +That suggests the behavior is not triggered simply by “some SwiftUI view exists nearby.” It appears to depend on the actual rendering/hosting model of the bar content. For example, SwiftUI-backed Expo UI content has been observed to adapt correctly because Expo UI mounts SwiftUI content through its own Fabric/SwiftUI virtual-view host, while regular RN/Fabric text and views remain ordinary UIKit views with JS-defined styling. + +### RN children vs SwiftUI-backed children + +The following example shows both approaches. The first bottom bar uses regular React Native primitives. The second uses SwiftUI-backed controls from `@expo/ui/swift-ui`. + +Regular RN children are flexible and require no extra dependency: + +```tsx +import { + PlatformColor, + ScrollView, + StyleSheet, + Switch, + Text, + View, +} from 'react-native'; +import { ScrollEdgeBar } from 'react-native-scroll-edge-bar'; + +export function RNBottomBarExample() { + return ( + + {/* content */} + + + Test + + + + Reset + + + + ); +} + +const styles = StyleSheet.create({ + bottomBar: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + paddingHorizontal: 16, + paddingVertical: 12, + backgroundColor: 'transparent', + }, + label: { + fontSize: 15, + fontWeight: '600', + color: PlatformColor('label'), + }, + spacer: { + flex: 1, + }, + button: { + paddingHorizontal: 14, + paddingVertical: 8, + borderRadius: 10, + backgroundColor: PlatformColor('secondarySystemFill'), + }, + buttonText: { + fontSize: 13, + fontWeight: '600', + color: PlatformColor('label'), + }, +}); +``` + +SwiftUI-backed controls can participate in the standalone scroll-edge transition more like native UIKit/SwiftUI controls: + +```tsx +import { ScrollView } from 'react-native'; +import { Button, HStack, Host, Spacer, Text, Toggle } from '@expo/ui/swift-ui'; +import { + buttonStyle, + controlSize, + font, + foregroundStyle, + padding, +} from '@expo/ui/swift-ui/modifiers'; +import { ScrollEdgeBar } from 'react-native-scroll-edge-bar'; + +export function SwiftUIBottomBarExample() { + return ( + + {/* content */} + + + + + + Test + + + +