Observe content view bounds natively to update sheet height on iOS
Replace the async JS bridge round-trip (onLayout → updateLayout()) with native KVO observation on the content view's bounds. When React Native's Yoga layout engine sets the frame, the KVO callback fires synchronously on the same run loop iteration and calls updateLayout() directly. This eliminates the timing issues that surfaced after enabling iOS 26 design. https://claude.ai/code/session_01Rp7ef1h3fKh6fhStcjqLJ5
This commit is contained in:
@@ -8,6 +8,9 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
|
||||
private var innerView: UIView?
|
||||
private var touchHandler: RCTTouchHandler?
|
||||
|
||||
// Native content height observation (eliminates JS bridge round-trip)
|
||||
private var contentHeightObservation: NSKeyValueObservation?
|
||||
|
||||
// Events
|
||||
private let onAttemptDismiss = EventDispatcher()
|
||||
private let onSnapPointChange = EventDispatcher()
|
||||
@@ -106,6 +109,8 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
|
||||
}
|
||||
|
||||
private func destroy() {
|
||||
self.contentHeightObservation?.invalidate()
|
||||
self.contentHeightObservation = nil
|
||||
self.isClosing = false
|
||||
self.isOpen = false
|
||||
self.sheetVc = nil
|
||||
@@ -147,6 +152,7 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
|
||||
|
||||
self.sheetVc = sheetVc
|
||||
self.isOpening = true
|
||||
self.startObservingContentHeight()
|
||||
|
||||
rvc.present(sheetVc, animated: true) { [weak self] in
|
||||
self?.isOpening = false
|
||||
@@ -154,6 +160,27 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// Observe the content view's bounds via KVO so that height changes are detected
|
||||
// purely on the native side, without a JS bridge round-trip through onLayout.
|
||||
private func startObservingContentHeight() {
|
||||
self.contentHeightObservation?.invalidate()
|
||||
|
||||
guard let contentView = self.innerView?.subviews.first else { return }
|
||||
|
||||
self.contentHeightObservation = contentView.observe(
|
||||
\.bounds,
|
||||
options: [.old, .new]
|
||||
) { [weak self] _, change in
|
||||
guard let self = self,
|
||||
(self.isOpen || self.isOpening) && !self.isClosing,
|
||||
let oldBounds = change.oldValue,
|
||||
let newBounds = change.newValue,
|
||||
oldBounds.height != newBounds.height,
|
||||
newBounds.height > 0 else { return }
|
||||
self.updateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout() {
|
||||
// Allow updates either when identifiers match OR when prevLayoutDetentIdentifier is nil (first real content update)
|
||||
if self.prevLayoutDetentIdentifier == self.selectedDetentIdentifier || self.prevLayoutDetentIdentifier == nil,
|
||||
|
||||
@@ -126,9 +126,9 @@ export class BottomSheetNativeComponent extends React.Component<
|
||||
// a console.log() - just sending the log to the CLI is enough.
|
||||
// TODO: Get to the bottom of this and fix it properly! -sfn
|
||||
setTimeout(() => this.updateLayout())
|
||||
} else {
|
||||
this.updateLayout()
|
||||
}
|
||||
// iOS: content height changes are observed natively via KVO on
|
||||
// the content view's bounds, so no JS bridge round-trip needed.
|
||||
}}
|
||||
/>
|
||||
</Portal>
|
||||
|
||||
Reference in New Issue
Block a user