diff --git a/ios/RNIModalSheetView/RNIModalSheetView.mm b/ios/RNIModalSheetView/RNIModalSheetView.mm index d95af2e9..9cb9db75 100644 --- a/ios/RNIModalSheetView/RNIModalSheetView.mm +++ b/ios/RNIModalSheetView/RNIModalSheetView.mm @@ -8,19 +8,17 @@ #import "RNIModalSheetView.h" #import "react-native-ios-modal/Swift.h" -#import +#import "react-native-ios-utilities/RNIBaseView.h" +#import "react-native-ios-utilities/RNIContentViewParentDelegate.h" -#import - - -#import -#import +#import "react-native-ios-utilities/UIApplication+RNIHelpers.h" +#import "react-native-ios-utilities/RNIObjcUtils.h" #if RCT_NEW_ARCH_ENABLED #include "RNIModalSheetViewComponentDescriptor.h" -#include -#include +#include "react-native-ios-utilities/RNIBaseViewState.h" +#include "react-native-ios-utilities/RNIBaseViewProps.h" #import #import diff --git a/ios/RNIModalSheetView/RNIModalSheetViewController.swift b/ios/RNIModalSheetView/RNIModalSheetViewController.swift new file mode 100644 index 00000000..d7c1bd96 --- /dev/null +++ b/ios/RNIModalSheetView/RNIModalSheetViewController.swift @@ -0,0 +1,117 @@ +// +// RNIModalSheetViewController.swift +// react-native-ios-modal +// +// Created by Dominic Go on 9/26/24. +// + +import UIKit +import react_native_ios_utilities + +#if !RCT_NEW_ARCH_ENABLED +import React +#endif + + +open class RNIModalSheetViewController: UIViewController { + + public var shouldTriggerDefaultCleanup = true; + + public weak var mainSheetContentParent: RNIContentViewParentDelegate?; + private(set) public weak var mainSheetContent: RNIWrapperViewContent?; + + public var positionConfigForMainSheetContent: AlignmentPositionConfig = .default; + + // MARK: - View Controller Lifecycle + // --------------------------------- + + public override func viewDidLoad() { + + guard let mainSheetContentParent = self.mainSheetContentParent, + let mainSheetContent = mainSheetContentParent.contentDelegate as? RNIWrapperViewContent + else { + return; + }; + + self.mainSheetContent = mainSheetContent; + mainSheetContentParent.reactViewLifecycleDelegates.add(self); + + // MARK: Setup Constraints + #if !RCT_NEW_ARCH_ENABLED + rootReactView.removeAllAncestorConstraints(); + #endif + + self.view.addSubview(mainSheetContent); + mainSheetContent.translatesAutoresizingMaskIntoConstraints = false; + + let constraints = self.positionConfigForMainSheetContent.createConstraints( + forView: mainSheetContent, + attachingTo: self.view, + enclosingView: self.view + ); + + NSLayoutConstraint.activate(constraints); + + // MARK: Set Initial Size + let hasValidSize = !self.view.bounds.size.isZero; + if hasValidSize { + self.positionConfigForMainSheetContent.setIntrinsicContentSizeOverrideIfNeeded( + forRootReactView: mainSheetContentParent, + withSize: self.view.bounds.size + ); + }; + + let shouldSetSize = + hasValidSize + && self.positionConfigForMainSheetContent.isStretchingOnBothAxis; + + if shouldSetSize { + self.positionConfigForMainSheetContent.applySize( + toRootReactView: mainSheetContentParent, + attachingTo: self.view + ); + }; + }; + + public override func viewDidLayoutSubviews() { + guard let mainSheetContentParent = self.mainSheetContentParent else { + return; + }; + + self.positionConfigForMainSheetContent.setIntrinsicContentSizeOverrideIfNeeded( + forRootReactView: mainSheetContentParent, + withSize: self.view.bounds.size + ); + + self.positionConfigForMainSheetContent.applySize( + toRootReactView: mainSheetContentParent, + attachingTo: self.view + ); + }; + + // MARK: Methods + // -------------- +}; + +extension RNIModalSheetViewController: RNIViewLifecycle { + + public func notifyOnRequestForCleanup(sender: RNIContentViewParentDelegate) { + guard self.shouldTriggerDefaultCleanup, + self.view.window != nil + else { + return; + }; + + if self.presentingViewController != nil { + self.dismiss(animated: true); + + } else if self.parent != nil { + self.willMove(toParent: nil); + self.view.removeFromSuperview(); + self.removeFromParent(); + + } else { + self.view.removeFromSuperview(); + }; + }; +}; diff --git a/ios/RNIModalSheetView/RNIModalSheetViewDelegate.swift b/ios/RNIModalSheetView/RNIModalSheetViewDelegate.swift index 30d08aaf..f583e44e 100644 --- a/ios/RNIModalSheetView/RNIModalSheetViewDelegate.swift +++ b/ios/RNIModalSheetView/RNIModalSheetViewDelegate.swift @@ -6,8 +6,8 @@ // import UIKit -import react_native_ios_utilities import DGSwiftUtilities +import react_native_ios_utilities @objc(RNIModalSheetViewDelegate) public final class RNIModalSheetViewDelegate: UIView, RNIContentView { @@ -30,6 +30,8 @@ public final class RNIModalSheetViewDelegate: UIView, RNIContentView { public weak var parentReactView: RNIContentViewParentDelegate?; + public var detachedModalContentParentViews: [RNIContentViewParentDelegate] = []; + // MARK: Properties - Props // ------------------------ @@ -60,25 +62,6 @@ public final class RNIModalSheetViewDelegate: UIView, RNIContentView { parentReactView.setSize(.init(width: 300, height: 300)); }; }; - - func _setupContent(){ - self.backgroundColor = .systemPink; - - let label = UILabel(); - label.text = "Fabric View (sort of) in Swift"; - - label.translatesAutoresizingMaskIntoConstraints = false; - self.addSubview(label); - - NSLayoutConstraint.activate([ - label.centerXAnchor.constraint( - equalTo: self.centerXAnchor - ), - label.centerYAnchor.constraint( - equalTo: self.centerYAnchor - ), - ]); - }; }; extension RNIModalSheetViewDelegate: RNIContentViewDelegate { @@ -87,10 +70,6 @@ extension RNIModalSheetViewDelegate: RNIContentViewDelegate { // MARK: Paper + Fabric // -------------------- - - public func notifyOnInit(sender: RNIContentViewParentDelegate) { - self._setupContent(); - }; public func notifyOnMountChildComponentView( sender: RNIContentViewParentDelegate, @@ -98,12 +77,23 @@ extension RNIModalSheetViewDelegate: RNIContentViewDelegate { index: NSInteger, superBlock: () -> Void ) { - #if !RCT_NEW_ARCH_ENABLED - superBlock(); - #endif + + guard let parentReactView = parentReactView else { + return; + }; + + defer { + parentReactView.requestToRemoveReactSubview(childComponentView); + childComponentView.removeFromSuperview(); + }; + + guard let reactView = childComponentView as? RNIContentViewParentDelegate, + reactView.contentDelegate is RNIWrapperViewContent + else { + return; + }; - // Note: Window might not be available yet - self.addSubview(childComponentView); + self.detachedModalContentParentViews.append(reactView); }; public func notifyOnUnmountChildComponentView( @@ -137,7 +127,48 @@ extension RNIModalSheetViewDelegate: RNIContentViewDelegate { resolve resolveBlock: (NSDictionary) -> Void, reject rejectBlock: (String) -> Void ) { - // no-op + + do { + guard let commandArguments = commandArguments as? Dictionary else { + throw RNIUtilitiesError(errorCode: .guardCheckFailed); + }; + + switch commandName { + case "presentModal": + let closestVC = + self.recursivelyFindNextResponder(withType: UIViewController.self); + + guard let closestVC = closestVC else { + throw RNIUtilitiesError(errorCode: .unexpectedNilValue); + }; + + let mainSheetContentParent = + self.detachedModalContentParentViews.first; + + guard let mainSheetContentParent = mainSheetContentParent else { + throw RNIUtilitiesError(errorCode: .unexpectedNilValue); + }; + + let isAnimated: Bool = commandArguments.getValueFromDictionary( + forKey: "isAnimated", + fallbackValue: true + ); + + let modalVC = RNIModalSheetViewController(); + modalVC.mainSheetContentParent = mainSheetContentParent; + modalVC.view.backgroundColor = .systemBackground; + + closestVC.present(modalVC, animated: isAnimated); + + resolveBlock([:]); + + default: + throw RNIUtilitiesError(errorCode: .invalidValue); + }; + + } catch { + rejectBlock(error.localizedDescription); + }; }; // MARK: Fabric Only diff --git a/ios/RNIModalSheetView/RNIModalSheetViewManager.mm b/ios/RNIModalSheetView/RNIModalSheetViewManager.mm index af896820..5eed26b5 100644 --- a/ios/RNIModalSheetView/RNIModalSheetViewManager.mm +++ b/ios/RNIModalSheetView/RNIModalSheetViewManager.mm @@ -8,8 +8,6 @@ #import "RNIModalSheetView.h" #import -#import "react-native-ios-utilities/RNIBaseViewUtils.h" - #import "RCTBridge.h" #import #import diff --git a/ios/RNIModalSheetView/RNIModalSheetViewShadowNode.h b/ios/RNIModalSheetView/RNIModalSheetViewShadowNode.h index 25abcd07..268c59fe 100644 --- a/ios/RNIModalSheetView/RNIModalSheetViewShadowNode.h +++ b/ios/RNIModalSheetView/RNIModalSheetViewShadowNode.h @@ -8,9 +8,9 @@ #if __cplusplus #pragma once -#include -#include -#include +#include "react-native-ios-utilities/RNIBaseViewShadowNode.h" +#include "react-native-ios-utilities/RNIBaseViewProps.h" +#include "react-native-ios-utilities/RNIBaseViewEventEmitter.h" #include #include diff --git a/src/components/ModalSheetView/ModalSheetView.tsx b/src/components/ModalSheetView/ModalSheetView.tsx index 6f013ddf..b2b654ff 100644 --- a/src/components/ModalSheetView/ModalSheetView.tsx +++ b/src/components/ModalSheetView/ModalSheetView.tsx @@ -21,7 +21,15 @@ export const ModalSheetView = React.forwardRef< ] = React.useState({}); React.useImperativeHandle(ref, () => ({ - presentModal: async () => { + presentModal: async (commandArgs) => { + if(nativeRef.current == null) { + throw Error("Unable to get ref to native sheet"); + }; + + await nativeRef.current.presentModal({ + isAnimated: true, + ...commandArgs, + }); }, })); diff --git a/src/components/ModalSheetView/ModalSheetViewNativeIDKeys.ts b/src/components/ModalSheetView/ModalSheetViewNativeIDKeys.ts new file mode 100644 index 00000000..3d314e3e --- /dev/null +++ b/src/components/ModalSheetView/ModalSheetViewNativeIDKeys.ts @@ -0,0 +1,5 @@ + + +export const MODAL_SHEET_VIEW_CONTENT_NATIVE_ID_KEYS = Object.freeze({ + mainSheetContent: 'mainSheetContent', +}); \ No newline at end of file diff --git a/src/components/ModalSheetView/ModalSheetViewTypes.tsx b/src/components/ModalSheetView/ModalSheetViewTypes.tsx index 88c04adc..14546961 100644 --- a/src/components/ModalSheetView/ModalSheetViewTypes.tsx +++ b/src/components/ModalSheetView/ModalSheetViewTypes.tsx @@ -1,12 +1,17 @@ import type { PropsWithChildren } from "react"; import type { ViewProps } from "react-native"; +import type { RemapObject } from "react-native-ios-utilities"; -import type { RNIModalSheetViewProps } from "../../native_components/RNIModalSheetVIew"; +import type { RNIModalSheetViewProps, RNIModalSheetViewRef } from "../../native_components/RNIModalSheetVIew"; -export type ModalSheetViewRef = { - presentModal: () => Promise; -}; +export type ModalSheetViewRef = RemapObject, { + presentModal: (commandArgs?: { + isAnimated?: boolean; + }) => Promise; +}>; export type ModalSheetViewInheritedProps = Pick { return viewID; }, - presentModal: async () => { + presentModal: async (commandArgs) => { if(viewID == null) return; const module = Helpers.getRNIUtilitiesModule(); await module.viewCommandRequest( /* viewID : */ viewID, /* commandName: */ 'presentModal', - /* commandArgs: */ {} + /* commandArgs: */ commandArgs, ); }, })); diff --git a/src/native_components/RNIModalSheetVIew/RNIModalSheetViewTypes.ts b/src/native_components/RNIModalSheetVIew/RNIModalSheetViewTypes.ts index 3f04edbd..0f9a8cb4 100644 --- a/src/native_components/RNIModalSheetVIew/RNIModalSheetViewTypes.ts +++ b/src/native_components/RNIModalSheetVIew/RNIModalSheetViewTypes.ts @@ -8,7 +8,10 @@ import type { RNIModalSheetNativeViewProps } from "./RNIModalSheetNativeView"; export type RNIModalSheetViewRef = { getViewID: () => StateViewID; getReactTag: () => StateReactTag; - presentModal: () => Promise; + + presentModal: (commandArgs: { + isAnimated: boolean; + }) => Promise; }; export type RNIModalSheetViewInheritedOptionalProps = Partial