react-native-true-sheet/ios/TrueSheetContentView.mm

208 lines
6.5 KiB
Plaintext

//
// Created by Jovanni Lo (@lodev09)
// Copyright (c) 2024-present. All rights reserved.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//
#ifdef RCT_NEW_ARCH_ENABLED
#import "TrueSheetContentView.h"
#import <React/RCTScrollViewComponentView.h>
#import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
#import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
#import <react/renderer/components/TrueSheetSpec/Props.h>
#import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
#import "TrueSheetView.h"
#import "TrueSheetViewController.h"
#import "utils/LayoutUtil.h"
using namespace facebook::react;
@implementation TrueSheetContentView {
RCTScrollViewComponentView *_pinnedScrollView;
UIView *_pinnedTopView;
CGSize _lastSize;
}
+ (ComponentDescriptorProvider)componentDescriptorProvider {
return concreteComponentDescriptorProvider<TrueSheetContentViewComponentDescriptor>();
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const TrueSheetContentViewProps>();
_props = defaultProps;
_pinnedScrollView = nil;
_pinnedTopView = nil;
_lastSize = CGSizeZero;
}
return self;
}
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
// Notify delegate when content size changes for sheet height updates
CGSize newSize = CGSizeMake(layoutMetrics.frame.size.width, layoutMetrics.frame.size.height);
if (!CGSizeEqualToSize(newSize, _lastSize)) {
_lastSize = newSize;
if ([self.delegate respondsToSelector:@selector(contentViewDidChangeSize:)]) {
[self.delegate contentViewDidChangeSize:newSize];
}
}
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
[super mountChildComponentView:childComponentView index:index];
if ([self.delegate respondsToSelector:@selector(contentViewDidChangeChildren)]) {
[self.delegate contentViewDidChangeChildren];
}
}
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
[super unmountChildComponentView:childComponentView index:index];
if ([self.delegate respondsToSelector:@selector(contentViewDidChangeChildren)]) {
[self.delegate contentViewDidChangeChildren];
}
}
- (void)unpinScrollViewFromParentView:(UIView *)parentView {
// Unpin previous scroll view if exists
if (_pinnedScrollView) {
[LayoutUtil unpinView:_pinnedScrollView fromParentView:parentView];
}
}
- (void)setupScrollViewPinning:(BOOL)pinned withHeaderView:(UIView *)headerView {
// Pin to container view (parent of content view)
UIView *containerView = self.superview;
if (!pinned) {
[self unpinScrollViewFromParentView:containerView];
_pinnedScrollView = nil;
_pinnedTopView = nil;
return;
}
// Auto-detect and pin scroll views for proper sheet scrolling behavior
// Pinning ensures ScrollView fills the available area and scrolls correctly with the sheet
UIView *topSibling = nil;
RCTScrollViewComponentView *scrollView = [self findScrollView:&topSibling];
// Use closest top sibling if found, otherwise fall back to header view
UIView *topView = topSibling ?: headerView;
// Re-pin when scroll view or top view changes
BOOL scrollViewChanged = scrollView != _pinnedScrollView;
BOOL topViewChanged = topView != _pinnedTopView;
if (scrollView && containerView && (scrollViewChanged || topViewChanged)) {
// Unpin first to remove old constraints
[self unpinScrollViewFromParentView:containerView];
if (topView) {
// Pin ScrollView below the top view
[LayoutUtil pinView:scrollView
toParentView:containerView
withTopView:topView
edges:UIRectEdgeLeft | UIRectEdgeRight | UIRectEdgeBottom];
} else {
// No top view, pin to all edges of container
[LayoutUtil pinView:scrollView toParentView:containerView edges:UIRectEdgeAll];
}
_pinnedScrollView = scrollView;
_pinnedTopView = topView;
} else if (!scrollView && _pinnedScrollView) {
// ScrollView was removed, clean up
[self unpinScrollViewFromParentView:containerView];
_pinnedScrollView = nil;
_pinnedTopView = nil;
}
}
- (RCTScrollViewComponentView *)findScrollViewInSubviews:(NSArray<UIView *> *)subviews {
for (UIView *subview in subviews) {
if ([subview isKindOfClass:TrueSheetView.class]) {
continue;
}
if ([subview isKindOfClass:RCTScrollViewComponentView.class]) {
return (RCTScrollViewComponentView *)subview;
}
}
return nil;
}
- (RCTScrollViewComponentView *)findScrollView:(UIView **)outTopSibling {
if (self.subviews.count == 0) {
return nil;
}
UIView *topSibling = nil;
// Check first-level children for scroll views (ScrollView or FlatList)
RCTScrollViewComponentView *scrollView = [self findScrollViewInSubviews:self.subviews];
// If not found, check second level (grandchildren)
if (!scrollView) {
for (UIView *subview in self.subviews) {
scrollView = [self findScrollViewInSubviews:subview.subviews];
if (scrollView) {
break;
}
}
}
// Find the view positioned directly above the ScrollView (only for first-level)
if (scrollView && scrollView.superview == self && self.subviews.count > 1) {
CGFloat scrollViewTop = CGRectGetMinY(scrollView.frame);
CGFloat closestDistance = CGFLOAT_MAX;
for (UIView *sibling in self.subviews) {
if (sibling == scrollView || [sibling isKindOfClass:TrueSheetView.class]) {
continue;
}
CGFloat siblingBottom = CGRectGetMaxY(sibling.frame);
if (siblingBottom <= scrollViewTop) {
CGFloat distance = scrollViewTop - siblingBottom;
if (distance < closestDistance) {
closestDistance = distance;
topSibling = sibling;
}
}
}
}
if (outTopSibling) {
*outTopSibling = topSibling;
}
return scrollView;
}
- (void)prepareForRecycle {
[super prepareForRecycle];
// Remove scroll view constraints
if (_pinnedScrollView) {
[LayoutUtil unpinView:_pinnedScrollView fromParentView:self.superview];
_pinnedScrollView = nil;
_pinnedTopView = nil;
}
}
@end
Class<RCTComponentViewProtocol> TrueSheetContentViewCls(void) {
return TrueSheetContentView.class;
}
#endif