Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 116 lines 5.1 kB view raw view rendered
1# expo-scroll-forwarder 2 3An Expo native module that forwards scroll gestures from a UIView to a UIScrollView on iOS. This enables custom scroll behaviors by allowing a non-scrollable view to control a scrollable view's scroll position. 4 5## What It Does 6 7This module solves a specific interaction problem: allowing a fixed header or overlay view to respond to scroll gestures and forward them to an underlying scroll view. The primary use case in the Bluesky app is the profile screen, where the profile header sits above a scrollable content area and can be dragged to scroll the content below it. 8 9Key behaviors: 10- Captures pan gestures on a wrapper view and translates them to scroll offsets on a target scroll view 11- Implements physics-based deceleration animations that match native scroll behavior 12- Supports pull-to-refresh interactions with haptic feedback 13- Prevents gesture conflicts with iOS swipe-back navigation by only activating on vertical pans 14- Provides rubber-band damping when scrolling past content bounds 15 16## Architecture 17 18The module consists of three main parts: 19 20### 1. Native iOS Implementation (Swift) 21 22**ExpoScrollForwarderView.swift** - The core native view component that: 23- Attaches a UIPanGestureRecognizer to intercept scroll gestures 24- Finds and references the target RCTScrollView using its React Native tag 25- Implements custom scroll physics including velocity-based decay animation 26- Manages gesture recognizer delegation to prevent conflicts with system gestures 27- Handles pull-to-refresh activation at -130pt scroll offset with haptic feedback 28 29**ExpoScrollForwarderModule.swift** - The Expo module definition that: 30- Registers the view component with Expo 31- Exposes the `scrollViewTag` prop to specify which scroll view to control 32 33### 2. TypeScript Interface 34 35**ExpoScrollForwarderView.tsx** - Platform-specific implementations: 36- **iOS (.ios.tsx)**: Wraps the native view manager from expo-modules-core 37- **Default (.tsx)**: No-op wrapper that just renders children (for Android/Web compatibility) 38 39**ExpoScrollForwarder.types.ts** - TypeScript type definitions: 40- `scrollViewTag`: The React Native tag of the scroll view to control 41- `children`: The content to render (typically a header component) 42 43### 3. Module Configuration 44 45**expo-module.config.json** - Declares iOS-only platform support 46 47**ExpoScrollForwarder.podspec** - CocoaPods specification for iOS dependency management 48 49## Usage 50 51```tsx 52import {ExpoScrollForwarderView} from 'expo-scroll-forwarder' 53 54function ProfileScreen() { 55 const scrollViewTag = useRef(null) 56 57 return ( 58 <View> 59 <ExpoScrollForwarderView scrollViewTag={scrollViewTag.current}> 60 <ProfileHeader /> 61 </ExpoScrollForwarderView> 62 63 <ScrollView ref={scrollViewTag}> 64 {/* Scrollable content */} 65 </ScrollView> 66 </View> 67 ) 68} 69``` 70 71The `scrollViewTag` prop must be the React Native tag (numeric identifier) of the target scroll view. The module uses this to locate the native UIScrollView instance. 72 73## Platform Support 74 75- **iOS**: Full native implementation with custom scroll physics 76- **Android**: No-op wrapper (renders children without scroll forwarding) 77- **Web**: No-op wrapper (renders children without scroll forwarding) 78 79The module is designed to enhance iOS UX while gracefully degrading on other platforms. 80 81## Key Implementation Details 82 83### Gesture Recognition 84- Only activates when pan velocity is more vertical than horizontal (`abs(velocity.y) > abs(velocity.x)`) 85- Delegates to UIGestureRecognizerDelegate to prevent simultaneous recognition with navigation swipe-back 86- Adds tap/long-press recognizers to the scroll view to cancel ongoing animations 87 88### Scroll Physics 89- Implements custom decay animation at 120fps using a Timer 90- Velocity decay factor: 0.9875 per frame 91- Velocity clamped to +/- 5000 points/second 92- Rubber-band damping: offsets below 0 are reduced by 55% 93- Animation stops when velocity drops below 5 points/second 94 95### Pull-to-Refresh 96- Triggers at -130pt scroll offset 97- Provides haptic feedback (UIImpactFeedbackGenerator, light style) 98- Calls refresh control via `RCTRefreshControl.forwarderBeginRefreshing()` 99 100### Scroll View Management 101- Dynamically finds scroll view using `AppContext.findView(withTag:ofType:)` 102- Properly cleans up gesture recognizers when switching between scroll views 103- Maintains references to both the scroll view and its refresh control 104 105## Files Overview 106 107| File | Purpose | 108|------|---------| 109| `ios/ExpoScrollForwarderView.swift` | Native iOS view implementation with gesture handling and scroll physics | 110| `ios/ExpoScrollForwarderModule.swift` | Expo module registration and prop definitions | 111| `ios/ExpoScrollForwarder.podspec` | CocoaPods dependency specification | 112| `src/ExpoScrollForwarderView.ios.tsx` | TypeScript wrapper for iOS native view | 113| `src/ExpoScrollForwarderView.tsx` | Default no-op implementation for other platforms | 114| `src/ExpoScrollForwarder.types.ts` | TypeScript type definitions | 115| `index.ts` | Module entry point | 116| `expo-module.config.json` | Expo module configuration |