Skip to content

Commit

Permalink
⭐️ Impl: ModalFocusEventNotifiable
Browse files Browse the repository at this point in the history
  • Loading branch information
dominicstop committed Oct 2, 2024
1 parent 1f89a48 commit 82b382e
Show file tree
Hide file tree
Showing 10 changed files with 680 additions and 1 deletion.
4 changes: 4 additions & 0 deletions ios/RNIModalSheetView/RNIModalSheetViewDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ extension RNIModalSheetViewDelegate: RNIContentViewDelegate {

// MARK: Paper + Fabric
// --------------------

public func notifyOnInit(sender: RNIContentViewParentDelegate) {
ModalEventsManagerRegistry.shared.swizzleIfNeeded();
}

public func notifyOnMountChildComponentView(
sender: RNIContentViewParentDelegate,
Expand Down
149 changes: 149 additions & 0 deletions ios/Temp/ModalEventManager/ModalEventsManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//
// ModalEventsManager.swift
// react-native-ios-modal
//
// Created by Dominic Go on 10/2/24.
//

import UIKit
import DGSwiftUtilities


public final class ModalEventsManager {

// MARK: - Properties
// ------------------

public weak var window: UIWindow?;
private(set) public var modalRegistry: ModalRegistry = .init();

// MARK: - Init
// ------------

public init(withWindow window: UIWindow){
self.window = window;
};

func registerModalIfNeeded(
_ modalVC: UIViewController,
isAboutToBePresented: Bool
){
let window =
self.window
?? modalVC.view.window
?? UIApplication.shared.activeWindow;

guard let window = window else {
return;
};

if self.window == nil {
self.window = window;
};

let currentModalLevel = window.currentModalLevel ?? -1;

let nextModalFocusIndex = isAboutToBePresented
? currentModalLevel + 1
: modalVC.modalLevel ?? -1;

self.modalRegistry.registerModalIfNeeded(
modalVC,
modalFocusIndex: nextModalFocusIndex
);
};

// MARK: - Methods Invoked Via Swizzling
// -------------------------------------

public func notifyOnModalWillPresent(
forViewController modalVC: UIViewController,
targetWindow: UIWindow?
){

guard let targetWindow = targetWindow else {
return
};

let eventManager =
ModalEventsManagerRegistry.shared.getManager(forWindow: targetWindow);

eventManager.registerModalIfNeeded(
modalVC,
isAboutToBePresented: true
);

let modalEntries = eventManager.modalRegistry.getEntriesGrouped();

modalEntries.topMostModal!.setModalFocusState(.focusing);
modalEntries.secondTopMostModal?.setModalFocusState(.blurring);

modalEntries.otherModals?.forEach {
$0.setModalFocusState(.blurred);
};
};

public func notifyOnModalDidPresent(
forViewController modalVC: UIViewController,
targetWindow: UIWindow?
){
guard let targetWindow = targetWindow else {
return
};

let eventManager =
ModalEventsManagerRegistry.shared.getManager(forWindow: targetWindow);

let modalEntries = eventManager.modalRegistry.getEntriesGrouped();

modalEntries.topMostModal!.setModalFocusState(.focused);
modalEntries.secondTopMostModal?.setModalFocusState(.blurred);

modalEntries.otherModals?.forEach {
$0.setModalFocusState(.blurred);
};
};

public func notifyOnModalWillDismiss(
forViewController modalVC: UIViewController,
targetWindow: UIWindow?
){
guard let targetWindow = targetWindow else {
return
};

let eventManager =
ModalEventsManagerRegistry.shared.getManager(forWindow: targetWindow);

let modalEntries = eventManager.modalRegistry.getEntriesGrouped();

modalEntries.topMostModal!.setModalFocusState(.blurring);
modalEntries.secondTopMostModal?.setModalFocusState(.focusing);

modalEntries.otherModals?.forEach {
$0.setModalFocusState(.blurred);
};
};

public func notifyOnModalDidDismiss(
forViewController modalVC: UIViewController,
targetWindow: UIWindow?
){
guard let targetWindow = targetWindow else {
return
};

let eventManager =
ModalEventsManagerRegistry.shared.getManager(forWindow: targetWindow);

let modalEntries = eventManager.modalRegistry.getEntriesGrouped();

modalEntries.topMostModal!.setModalFocusState(.blurred);
modalEntries.secondTopMostModal?.setModalFocusState(.focused);

modalEntries.otherModals?.forEach {
$0.setModalFocusState(.blurred);
};
};
};

148 changes: 148 additions & 0 deletions ios/Temp/ModalEventManager/ModalEventsManagerRegistry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//
// ModalEventsManagerRegistry.swift
// react-native-ios-modal
//
// Created by Dominic Go on 10/2/24.
//

import UIKit
import DGSwiftUtilities


public final class ModalEventsManagerRegistry: Singleton {

public typealias `Self` = ModalEventsManagerRegistry;

// MARK: Static Properties
// -----------------------

public static let shared: Self = .init();

// MARK: - Static Properties (Swizzling-Related)
// ---------------------------------------------

private(set) public static var isSwizzled = false;
public static var shouldSwizzle = true;

// MARK: - Properties
// ------------------

public var instanceRegistry: Dictionary<String, ModalEventsManager> = [:]

// MARK: - Init
// ------------

public init(){
self.swizzleIfNeeded();
};

// MARK: - Methods
// ---------------

public func getManager(forWindow window: UIWindow) -> ModalEventsManager {
let match = self.instanceRegistry[window.synthesizedStringID];


if let match = match,
match.window != nil
{
return match;
};

let newManager: ModalEventsManager = .init(withWindow: window);
self.instanceRegistry[window.synthesizedStringID] = newManager;

return newManager;
};

// MARK: - Methods (Swizzling-Related)
// ----------------------------------

public func swizzleIfNeeded(){
guard Self.shouldSwizzle,
!Self.isSwizzled
else { return };

Self.isSwizzled = true;
self._swizzlePresent();
self._swizzleDismiss();
};

private func _swizzlePresent(){
SwizzlingHelpers.swizzlePresent() { originalImp, selector in
return { _self, vcToPresent, animated, completion in

let currentWindow =
_self.view.window
?? vcToPresent.view.window
?? UIApplication.shared.activeWindow;

guard let currentWindow = currentWindow else {
#if DEBUG
fatalError("Unable to get window")
#else
return;
#endif
};

let eventManager = self.getManager(forWindow: currentWindow);

eventManager.notifyOnModalWillPresent(
forViewController: vcToPresent,
targetWindow: currentWindow
);

// Call the original implementation.
originalImp(_self, selector, vcToPresent, animated){
eventManager.notifyOnModalDidPresent(
forViewController: vcToPresent,
targetWindow: currentWindow
);
completion?();
};
};
};
};

private func _swizzleDismiss(){
SwizzlingHelpers.swizzleDismiss() { originalImp, selector in
return { _self, animated, completion in

let currentWindow =
_self.view.window
?? _self.presentedViewController?.view.window
?? UIApplication.shared.activeWindow;

let modalVC =
_self.presentedViewController
?? currentWindow?.topmostPresentedViewController;

guard let currentWindow = currentWindow,
let modalVC = modalVC
else {
#if DEBUG
fatalError("Unable to get window or presented view controller")
#else
return;
#endif
};

let eventManager = self.getManager(forWindow: currentWindow);

eventManager.notifyOnModalWillDismiss(
forViewController: modalVC,
targetWindow: currentWindow
);

// Call the original implementation.
originalImp(_self, selector, animated){
eventManager.notifyOnModalDidDismiss(
forViewController: modalVC,
targetWindow: currentWindow
);
completion?();
};
};
};
};
};
31 changes: 31 additions & 0 deletions ios/Temp/ModalEventManager/ModalFocusEventNotifiable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// ModalFocusEventNotifiable.swift
//
//
// Created by Dominic Go on 6/15/24.
//

import Foundation

public protocol ModalFocusEventNotifiable: AnyObject {

func notifyForModalFocusStateChange(
prevState: ModalFocusState?,
currentState: ModalFocusState,
nextState: ModalFocusState
);
};

// MARK: - ModalFocusEventNotifiable+Default
// -----------------------------------------

public extension ModalFocusEventNotifiable {

func notifyForModalFocusStateChange(
prevState: ModalFocusState?,
currentState: ModalFocusState,
nextState: ModalFocusState
) {
// no-op
};
};
Loading

0 comments on commit 82b382e

Please sign in to comment.