From ab5b00bf63e5daab23363fd6bbe83bdc9d3fb81b Mon Sep 17 00:00:00 2001 From: Dominic Go <18517029+dominicstop@users.noreply.github.com> Date: Sat, 22 Apr 2023 08:05:33 +0800 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20Impl:=20Sheet-Related=20Ev?= =?UTF-8?q?ents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related: * `TODO:2023-04-20-23-58-24` - Impl. sheets + detents. * `TODO:2023-04-21-23-26-13` - Impl. modal event `onModalDetentDid`. * `TODO:2023-04-21-23-26-13` - Impl. modal event `onModalDetentDidCompute`. Summary: Impl. detent-related events. --- .../UISheetPresentationController+Init.swift | 23 +++- .../RNIModal/RNIModalEventData.swift | 9 ++ .../RNIModalView/RNIModalView.swift | 124 +++++++++++++++++- .../RNIModalView/RNIModalViewManager.m | 3 + src/components/ModalView/ModalView.tsx | 36 +++++ src/components/ModalView/ModalViewEmitter.ts | 6 + src/components/ModalView/ModalViewTypes.ts | 2 + .../RNIModalView/RNIModalViewEvents.ts | 19 +++ .../RNIModalView/RNIModalViewTypes.ts | 5 + 9 files changed, 220 insertions(+), 7 deletions(-) diff --git a/ios/src_library/Extensions+Init/UISheetPresentationController+Init.swift b/ios/src_library/Extensions+Init/UISheetPresentationController+Init.swift index e9f14345..2b176d8c 100644 --- a/ios/src_library/Extensions+Init/UISheetPresentationController+Init.swift +++ b/ios/src_library/Extensions+Init/UISheetPresentationController+Init.swift @@ -11,6 +11,16 @@ import Foundation @available(iOS 15.0, *) extension UISheetPresentationController.Detent { + // 2, + // _identifier=com.apple.UIKit.medium + // > + // + // 1, + // _identifier=com.apple.UIKit.large + // > + static func fromString( _ string: String ) -> UISheetPresentationController.Detent? { @@ -25,7 +35,18 @@ extension UISheetPresentationController.Detent { }; @available(iOS 15.0, *) -extension UISheetPresentationController.Detent.Identifier { +extension UISheetPresentationController.Detent.Identifier: + CustomStringConvertible { + + public var description: String { + switch self { + case .medium: return "medium"; + case .large : return "large"; + + default: return self.rawValue; + }; + }; + init?(fromSystemIdentifierString string: String) { switch string { case "medium": self = .medium; diff --git a/ios/src_library/React Native/RNIModal/RNIModalEventData.swift b/ios/src_library/React Native/RNIModal/RNIModalEventData.swift index 4f4a6043..9147b555 100644 --- a/ios/src_library/React Native/RNIModal/RNIModalEventData.swift +++ b/ios/src_library/React Native/RNIModal/RNIModalEventData.swift @@ -42,3 +42,12 @@ public struct RNIOnModalFocusEventData: RNIDictionarySynthesizable { public let isInitial: Bool; }; + +public struct RNIModalDidChangeSelectedDetentIdentifierEventData: RNIDictionarySynthesizable { + public let sheetDetentStringPrevious: String?; + public let sheetDetentStringCurrent: String?; +}; + +public struct RNIModalDetentDidComputeEventData: RNIDictionarySynthesizable { + public let maximumDetentValue: CGFloat; +}; diff --git a/ios/src_library/React Native/RNIModalView/RNIModalView.swift b/ios/src_library/React Native/RNIModalView/RNIModalView.swift index 541bb53b..0a4d3df5 100644 --- a/ios/src_library/React Native/RNIModalView/RNIModalView.swift +++ b/ios/src_library/React Native/RNIModalView/RNIModalView.swift @@ -31,6 +31,10 @@ public class RNIModalView: var modalContentWrapper: RNIWrapperView?; public var modalVC: RNIModalViewController?; + + public var sheetDetentStringCurrent: String?; + public var sheetDetentStringPrevious: String?; + // MARK: - Properties - RNIModalPresentationNotifying // -------------------------------------------------- @@ -88,6 +92,9 @@ public class RNIModalView: @objc var onPresentationControllerDidDismiss: RCTBubblingEventBlock?; @objc var onPresentationControllerDidAttemptToDismiss: RCTBubblingEventBlock?; + @objc var onModalDetentDidCompute: RCTBubblingEventBlock?; + @objc var onModalDidChangeSelectedDetentIdentifier: RCTBubblingEventBlock?; + // MARK: - Properties: React Props - General // ----------------------------------------- @@ -247,14 +254,25 @@ public class RNIModalView: @objc var sheetSelectedDetentIdentifier: String? { willSet { - guard #available(iOS 15.0, *), + let oldValue = self.sheetSelectedDetentIdentifier; + + guard oldValue != newValue, + #available(iOS 15.0, *), let sheetController = self.sheetPresentationController else { return }; + let nextDetentID = self.synthesizedSheetSelectedDetentIdentifier; + self.sheetAnimateChangesIfNeeded { - sheetController.selectedDetentIdentifier = - self.synthesizedSheetSelectedDetentIdentifier; + sheetController.selectedDetentIdentifier = nextDetentID; }; + + /// Delegate function does not get called when detent is changed via + /// setting `selectedDetentIdentifier`, so invoke manually... + /// + self.sheetPresentationControllerDidChangeSelectedDetentIdentifier( + sheetController + ); } }; @@ -355,13 +373,26 @@ public class RNIModalView: @available(iOS 15.0, *) public var synthesizedModalSheetDetents: [UISheetPresentationController.Detent]? { self.modalSheetDetents?.compactMap { - if let string = $0 as? String { - return UISheetPresentationController.Detent.fromString(string); + if let string = $0 as? String, + let detent = UISheetPresentationController.Detent.fromString(string) { + + return detent; } else if #available(iOS 16.0, *), let dict = $0 as? Dictionary { - let customDetent = RNIModalCustomSheetDetent(forDict: dict); + let customDetent = RNIModalCustomSheetDetent(forDict: dict) { + _, maximumDetentValue in + + let eventData = RNIModalDetentDidComputeEventData( + maximumDetentValue: maximumDetentValue + ); + + self.onModalDetentDidCompute?( + eventData.synthesizedJSDictionary + ); + }; + return customDetent?.synthesizedDetent; }; @@ -407,6 +438,7 @@ public class RNIModalView: // MARK: - Properties: Computed // ---------------------------- + // TODO: Move to `RNIModal+Helpers` @available(iOS 15.0, *) var sheetPresentationController: UISheetPresentationController? { guard let presentedVC = self.modalViewController else { return nil }; @@ -426,6 +458,27 @@ public class RNIModalView: }; }; + @available(iOS 15.0, *) + var currentSheetDetentID: UISheetPresentationController.Detent.Identifier? { + guard let sheetController = self.sheetPresentationController + else { return nil }; + + let detents = sheetController.detents; + + if let selectedDetent = sheetController.selectedDetentIdentifier { + return selectedDetent; + + } else if #available(iOS 16.0, *), + let firstDetent = detents.first { + + /// The default value of `selectedDetentIdentifier` is nil, which means + /// the sheet displays at the smallest detent you specify in detents. + return firstDetent.identifier; + }; + + return nil; + }; + // MARK: - Init // ------------ @@ -687,6 +740,7 @@ public class RNIModalView: if #available(iOS 15.0, *), let sheetController = self.sheetPresentationController { + sheetController.delegate = self; self.applyModalSheetProps(to: sheetController); }; @@ -726,6 +780,16 @@ public class RNIModalView: completion?(true, nil); + // let panGesture = self.modalVC? + // .presentationController? + // .presentedView? + // .gestureRecognizers? + // .first { + // $0 is UIPanGestureRecognizer + // }; + // + // panGesture?.addTarget(self, action: #selector(Self.handleGestureRecognizer(_:))) + #if DEBUG print( "Log - RNIModalView.presentModal - Present modal finished" @@ -737,6 +801,12 @@ public class RNIModalView: }; }; + // @objc func handleGestureRecognizer(_ sender: UIPanGestureRecognizer) { + // print( + // "Test - handleGestureRecognizer - \(sender.state.description)" + // ); + // }; + public func dismissModal(completion: CompletionHandler? = nil) { guard self.computedIsModalPresented else { #if DEBUG @@ -945,6 +1015,48 @@ extension RNIModalView: UIAdaptivePresentationControllerDelegate { }; }; +@available(iOS 15.0, *) +extension RNIModalView: UISheetPresentationControllerDelegate { + + /// `Note:2023-04-22-03-50-59` + /// + /// * This function gets invoked when the sheet has snapped into a detent + /// + /// * However, we don't get notified whenever the user is currently dragging + /// the sheet. + /// + /// * The `presentedViewController.transitionCoordinator` is only available + /// during modal presentation and dismissal. + /// + /// + public func sheetPresentationControllerDidChangeSelectedDetentIdentifier( + _ sheetPresentationController: UISheetPresentationController + ) { + let currentDetentID = self.currentSheetDetentID; + + self.sheetDetentStringPrevious = self.sheetDetentStringCurrent; + self.sheetDetentStringCurrent = currentDetentID?.description; + + #if DEBUG + print( + "Log - RNIModalView+UISheetPresentationControllerDelegate" + + " - sheetPresentationControllerDidChangeSelectedDetentIdentifier" + + " - sheetDetentStringPrevious: \(self.sheetDetentStringPrevious ?? "N/A")" + + " - sheetDetentStringCurrent: \(self.sheetDetentStringCurrent ?? "N/A")" + ); + #endif + + let eventData = RNIModalDidChangeSelectedDetentIdentifierEventData( + sheetDetentStringPrevious: self.sheetDetentStringPrevious, + sheetDetentStringCurrent: self.sheetDetentStringCurrent + ); + + self.onModalDidChangeSelectedDetentIdentifier?( + eventData.synthesizedJSDictionary + ); + }; +}; + // MARK: Extension: RNIModalRequestable // ------------------------------------ diff --git a/ios/src_library/React Native/RNIModalView/RNIModalViewManager.m b/ios/src_library/React Native/RNIModalView/RNIModalViewManager.m index 6470f54f..cc519dca 100644 --- a/ios/src_library/React Native/RNIModalView/RNIModalViewManager.m +++ b/ios/src_library/React Native/RNIModalView/RNIModalViewManager.m @@ -28,6 +28,9 @@ @interface RCT_EXTERN_MODULE(RNIModalViewManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onPresentationControllerDidDismiss, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPresentationControllerDidAttemptToDismiss, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onModalDetentDidCompute, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onModalDidChangeSelectedDetentIdentifier, RCTBubblingEventBlock); + // MARK: - Value Props - General // ----------------------------- diff --git a/src/components/ModalView/ModalView.tsx b/src/components/ModalView/ModalView.tsx index aedd1095..9165e5ed 100644 --- a/src/components/ModalView/ModalView.tsx +++ b/src/components/ModalView/ModalView.tsx @@ -37,6 +37,8 @@ import { RNIModalView, RNIModalBaseEvent, RNIModalDeprecatedBaseEvent, + OnModalDetentDidComputeEvent, + OnModalDidChangeSelectedDetentIdentifierEvent, } from '../../native_components/RNIModalView'; import { RNIModalViewModule } from '../../native_modules/RNIModalViewModule'; @@ -134,6 +136,8 @@ export class ModalView extends onPresentationControllerWillDismiss, onPresentationControllerDidDismiss, onPresentationControllerDidAttemptToDismiss, + onModalDetentDidCompute, + onModalDidChangeSelectedDetentIdentifier, // Component Props autoCloseOnUnmount, @@ -217,6 +221,8 @@ export class ModalView extends onPresentationControllerWillDismiss, onPresentationControllerDidDismiss, onPresentationControllerDidAttemptToDismiss, + onModalDetentDidCompute, + onModalDidChangeSelectedDetentIdentifier, // C - View-Related Props children, @@ -613,6 +619,34 @@ export class ModalView extends ); }; + private _handleOnModalDetentDidCompute: + OnModalDetentDidComputeEvent = (event) => { + + const props = this.props; + + props.onModalDetentDidCompute?.(event); + event.stopPropagation(); + + this.emitter.emit( + ModalViewEmitterEvents.onModalDetentDidCompute, + event.nativeEvent + ); + }; + + private _handleOnModalDidChangeSelectedDetentIdentifier: + OnModalDidChangeSelectedDetentIdentifierEvent = (event) => { + + const props = this.props; + + props.onModalDidChangeSelectedDetentIdentifier?.(event); + event.stopPropagation(); + + this.emitter.emit( + ModalViewEmitterEvents.onModalDidChangeSelectedDetentIdentifier, + event.nativeEvent + ); + }; + private _renderModal() { const { viewProps, ...props } = this.getProps(); const state = this.state; @@ -657,6 +691,8 @@ export class ModalView extends onPresentationControllerWillDismiss={this._handleOnPresentationControllerWillDismiss} onPresentationControllerDidDismiss={this._handleOnPresentationControllerDidDismiss} onPresentationControllerDidAttemptToDismiss={this._handleOnPresentationControllerDidAttemptToDismiss} + onModalDetentDidCompute={this._handleOnModalDetentDidCompute} + onModalDidChangeSelectedDetentIdentifier={this._handleOnModalDidChangeSelectedDetentIdentifier} {...overrideProps} {...viewProps} > diff --git a/src/components/ModalView/ModalViewEmitter.ts b/src/components/ModalView/ModalViewEmitter.ts index 9e42bd63..da80ba94 100644 --- a/src/components/ModalView/ModalViewEmitter.ts +++ b/src/components/ModalView/ModalViewEmitter.ts @@ -17,6 +17,8 @@ import type { OnPresentationControllerWillDismissEventObject, OnPresentationControllerDidDismissEventObject, OnPresentationControllerDidAttemptToDismissEventObject, + OnModalDetentDidComputeEventObject, + OnModalDidChangeSelectedDetentIdentifierEventObject, } from 'src/native_components/RNIModalView'; import type { KeyMapType } from '../../types/UtilityTypes'; @@ -43,6 +45,8 @@ export enum ModalViewEmitterEvents { onPresentationControllerWillDismiss = 'onPresentationControllerWillDismiss', onPresentationControllerDidDismiss = 'onPresentationControllerDidDismiss', onPresentationControllerDidAttemptToDismiss = 'onPresentationControllerDidAttemptToDismiss', + onModalDetentDidCompute = 'onModalDetentDidCompute', + onModalDidChangeSelectedDetentIdentifier = 'onModalDidChangeSelectedDetentIdentifier', onLayoutModalContentContainer = 'onLayoutModalContentContainer', } @@ -69,6 +73,8 @@ export type ModalViewEmitterEventMap = onPresentationControllerWillDismiss: OnPresentationControllerWillDismissEventObject['nativeEvent']; onPresentationControllerDidDismiss: OnPresentationControllerDidDismissEventObject['nativeEvent']; onPresentationControllerDidAttemptToDismiss: OnPresentationControllerDidAttemptToDismissEventObject['nativeEvent']; + onModalDetentDidCompute: OnModalDetentDidComputeEventObject['nativeEvent']; + onModalDidChangeSelectedDetentIdentifier: OnModalDidChangeSelectedDetentIdentifierEventObject['nativeEvent']; onLayoutModalContentContainer: LayoutChangeEvent['nativeEvent']; } diff --git a/src/components/ModalView/ModalViewTypes.ts b/src/components/ModalView/ModalViewTypes.ts index 1e9e4f90..49bf684d 100644 --- a/src/components/ModalView/ModalViewTypes.ts +++ b/src/components/ModalView/ModalViewTypes.ts @@ -58,6 +58,8 @@ export type ModalViewBaseProps = Partial< | 'onPresentationControllerWillDismiss' | 'onPresentationControllerDidDismiss' | 'onPresentationControllerDidAttemptToDismiss' + | 'onModalDetentDidCompute' + | 'onModalDidChangeSelectedDetentIdentifier' > > & { // TODO: See TODO:2023-03-04-13-02-45 - Refactor: Rename to diff --git a/src/native_components/RNIModalView/RNIModalViewEvents.ts b/src/native_components/RNIModalView/RNIModalViewEvents.ts index e405fa75..36a7b104 100644 --- a/src/native_components/RNIModalView/RNIModalViewEvents.ts +++ b/src/native_components/RNIModalView/RNIModalViewEvents.ts @@ -99,6 +99,16 @@ export type OnModalDidFocusEventObject = NativeSyntheticEvent< RNIOnModalFocusEvent & {} >; +export type OnModalDetentDidComputeEventObject = NativeSyntheticEvent<{ + maximumDetentValue: number; +}>; + +export type OnModalDidChangeSelectedDetentIdentifierEventObject = + NativeSyntheticEvent<{ + sheetDetentStringPrevious?: string; + sheetDetentStringCurrent?: string; + }>; + export type OnModalWillBlurEventObject = NativeSyntheticEvent< RNIOnModalFocusEvent & {} >; @@ -154,3 +164,12 @@ export type OnModalDidFocusEvent = (event: OnModalDidFocusEventObject) => void; export type OnModalWillBlurEvent = (event: OnModalWillBlurEventObject) => void; export type OnModalDidBlurEvent = (event: OnModalDidBlurEventObject) => void; + + +export type OnModalDetentDidComputeEvent = ( + event: OnModalDetentDidComputeEventObject +) => void; + +export type OnModalDidChangeSelectedDetentIdentifierEvent = ( + event: OnModalDidChangeSelectedDetentIdentifierEventObject +) => void; \ No newline at end of file diff --git a/src/native_components/RNIModalView/RNIModalViewTypes.ts b/src/native_components/RNIModalView/RNIModalViewTypes.ts index 093f0255..30c8497e 100644 --- a/src/native_components/RNIModalView/RNIModalViewTypes.ts +++ b/src/native_components/RNIModalView/RNIModalViewTypes.ts @@ -26,6 +26,8 @@ import type { OnPresentationControllerWillDismissEvent, OnPresentationControllerDidDismissEvent, OnPresentationControllerDidAttemptToDismissEvent, + OnModalDetentDidComputeEvent, + OnModalDidChangeSelectedDetentIdentifierEvent, } from './RNIModalViewEvents'; import type { UnionWithAutoComplete } from 'src/types/UtilityTypes'; @@ -103,6 +105,9 @@ export type RNIModalViewBaseProps = { onPresentationControllerWillDismiss: OnPresentationControllerWillDismissEvent; onPresentationControllerDidDismiss: OnPresentationControllerDidDismissEvent; onPresentationControllerDidAttemptToDismiss: OnPresentationControllerDidAttemptToDismissEvent; + + onModalDetentDidCompute: OnModalDetentDidComputeEvent; + onModalDidChangeSelectedDetentIdentifier: OnModalDidChangeSelectedDetentIdentifierEvent; }; export type RNIModalViewProps = Partial & RNIModalViewBaseProps;