A View never scrolls; taller content is cut off. ScrollView wraps its children in a scrollable container (a ScrollView on Android, a UIScrollView on iOS). It needs a bounded height, so give it flex: 1 inside a parent that has one; a missing flex: 1 up the tree is the usual cause of a list that will not scroll. Style the content with contentContainerStyle, since padding and gap belong to the inner container.
<ScrollView ref={scroller} contentContainerStyle={ui.screen} stickyHeaderIndices={[0]}
keyboardDismissMode="on-drag">
<Text style={[ui.h1, { backgroundColor: '#eceff1' }]}>Catalog</Text>
{[...books, ...books].map((b, i) => (
<Text key={i} style={[ui.card, { height: 120 }]}>{b.title}{'\n'}{b.summary}</Text>
))}
</ScrollView>stickyHeaderIndices={[0]} pins the first child while the rest scroll under it. The ref (useRef<ScrollView>(null)) gives you scrollTo({ x, y, animated }), scrollToEnd() and flashScrollIndicators(). Two props control the keyboard: keyboardDismissMode ('none', 'on-drag', or 'interactive' on iOS) and keyboardShouldPersistTaps. The latter defaults to 'never', so the first tap outside a focused field only closes the keyboard; set it to 'handled' whenever a scrolling form has buttons, or users must tap Save twice.