Skip to content

Commit

Permalink
⭐️ Impl: ModalSheetPresentationStateMachine Logic
Browse files Browse the repository at this point in the history
  • Loading branch information
dominicstop committed Sep 29, 2024
1 parent eac07cb commit b08364a
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 19 deletions.
165 changes: 146 additions & 19 deletions ios/Temp/ModalSheetPresentationStateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,22 @@ public class ModalSheetPresentationStateMachine {
public var didPresent = false;
public var didDismissAfterPresented = false;

public var isSheetPanGestureActive = false;

public var eventDelegates:
MulticastDelegate<ModalSheetPresentationStateEventsNotifiable> = .init();

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

public var isPresentingForTheFirstTime: Bool {
self.currentState.isPresenting
&& !self.didPresent;
};

public var didPresentForTheFirstTime: Bool {
self.currentState.isPresented
};

// MARK: - Methods
// ---------------
Expand All @@ -31,14 +45,19 @@ public class ModalSheetPresentationStateMachine {
let prevState = self.prevState;
let currentState = self.currentState;


print(
"setStateExplicit",
"\n - state: \(prevState?.rawValue ?? "N/A") -> \(currentState.rawValue) -> \(nextState.rawValue)",
"\n"
);

#if DEBUG
if Self._debugShouldLog {
print(
"ModalSheetPresentationStateMachine.\(#function) - PRE",
"\n - instance:", Unmanaged.passUnretained(self).toOpaque(),
"\n - prevState:", prevState?.rawValue ?? "N/A",
"\n - currentState:", currentState,
"\n - arg, nextState:", nextState,
"\n - state: \(prevState?.rawValue ?? "N/A") -> \(currentState.rawValue) -> \(nextState.rawValue)",
"\n - self.didPresent:", self.didPresent,
"\n - self.didDismissAfterPresented:", self.didDismissAfterPresented,
"\n"
Expand Down Expand Up @@ -97,24 +116,78 @@ public class ModalSheetPresentationStateMachine {
};

public func setState(nextState: ModalSheetState) {
var nextStateOverride: ModalSheetState? = nil;

switch (self.prevState, self.currentState, nextState) {
case (_, .draggingViaGesture, .presenting):
nextStateOverride = .dismissViaGestureCancelling;

case (.draggingViaGesture, .dismissViaGestureCancelling, let nextState)
where nextState.isPresented:

nextStateOverride = .dismissViaGestureCancelled;

case (.draggingViaGesture, .dismissingViaGesture, .dismissed):
nextStateOverride = .dismissedViaGesture;

default:
break;
};

let nextStateUpdated = nextStateOverride ?? nextState;

#if DEBUG
if Self._debugShouldLog {
print(
"ModalSheetPresentationStateMachine.\(#function)",
"\n - instance:", Unmanaged.passUnretained(self).toOpaque(),
"\n - arg, nextState:", nextState,
"\n - prevState:", self.prevState?.rawValue ?? "N/A",
"\n - currentState:", self.currentState.rawValue,
"\n - nextState, raw:", nextState,
"\n - nextState, override:", nextStateOverride?.rawValue ?? "N/A",
"\n"
);
};
#endif

switch (self.prevState, self.currentState, nextState) {

default:
break;
var isIllegalState = false;

/// Don't allow:
/// * `dismissingViaGesture` -> `dismissing`
/// * `dismissViaGestureCancelling` -> `presenting`
/// * `dismissedViaGesture` -> `dismissed`
/// * etc.
///
/// Keep state as specific as possible, i.e. don't overwrite specific state
/// w/ generic/simple state
///
if nextStateUpdated.isGeneric(comparedTo: self.currentState) {
return;
};

self.setStateExplicit(nextState: nextState);
/// Don't allow:
/// * `dismissingViaGesture` -> `dismissing` -> `dismissingViaGesture`
///
if self.currentState.isDraggingViaGesture,
nextStateUpdated == .dismissing,
self.isSheetPanGestureActive
{
return;
};

/// Don't allow:
/// * `dismissingViaGesture` -> `draggingViaGesture`
///
/// * happe
///
///
if self.currentState == .dismissingViaGesture,
nextStateUpdated == .draggingViaGesture
{
return;
};

self.setStateExplicit(nextState: nextStateUpdated);
};

public func reset(){
Expand All @@ -139,22 +212,17 @@ public class ModalSheetPresentationStateMachine {
// ---------------------

#if DEBUG
public static var _debugShouldLog = true;
public static var _debugShouldLog = false;
#endif
};

extension ModalSheetPresentationStateMachine: ViewControllerLifecycleNotifiable {

public func notifyOnViewDidLoad(sender: UIViewController) {
// no-op
};

public func notifyOnViewWillAppear(
sender: UIViewController,
isAnimated: Bool,
isFirstAppearance: Bool
) {

self.setState(nextState: .presenting);
};

Expand All @@ -163,7 +231,6 @@ extension ModalSheetPresentationStateMachine: ViewControllerLifecycleNotifiable
isAnimated: Bool,
isFirstAppearance: Bool
) {

self.setState(nextState: .presenting);
};

Expand All @@ -172,23 +239,83 @@ extension ModalSheetPresentationStateMachine: ViewControllerLifecycleNotifiable
isAnimated: Bool,
isFirstAppearance: Bool
) {

self.setState(nextState: .presented);
};

public func notifyOnViewWillDisappear(
sender: UIViewController,
isAnimated: Bool
) {

self.setState(nextState: .dismissing);
};

public func notifyOnViewDidDisappear(
sender: UIViewController,
isAnimated: Bool
) {

self.setState(nextState: .dismissed);
};
};

// MARK: - ModalSheetPresentationStateMachine+SheetViewControllerEventsNotifiable
// ------------------------------------------------------------------------------

extension ModalSheetPresentationStateMachine: ModalSheetViewControllerEventsNotifiable {

public func notifyOnSheetDidAttemptToDismissViaGesture(
sender: UIViewController,
presentationController: UIPresentationController
) {
self.setState(nextState: .presentingViaGestureCancelled);
};

public func notifyOnSheetDidDismissViaGesture(
sender: UIViewController,
presentationController: UIPresentationController
) {
self.setState(nextState: .dismissedViaGesture);
};

public func notifyOnSheetWillDismissViaGesture(
sender: UIViewController,
presentationController: UIPresentationController
) {
guard let transitionCoordinator = sender.transitionCoordinator else {
return;
};

transitionCoordinator.notifyWhenInteractionChanges {
guard sender.isBeingDismissed,
$0.isAnimated
else {
return;
};

self.setState(nextState: $0.isCancelled
? .dismissViaGestureCancelling
: .dismissingViaGesture
);
};
};

// TODO: Rename to `notifyOnSystemSheetPanGestureInvoked`
public func notifyOnSytemSheetPanGestureInvoked(
sender: UIViewController,
panGesture: UIPanGestureRecognizer,
gesturePoint: CGPoint
) {

switch panGesture.state {
case .began, .changed:
self.isSheetPanGestureActive = true;

case .ended, .cancelled, .failed:
self.isSheetPanGestureActive = false;

default:
break;
};

self.setState(nextState: .draggingViaGesture);
};
};
26 changes: 26 additions & 0 deletions ios/Temp/ModalSheetViewControllerLifecycleNotifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ open class ModalSheetViewControllerLifecycleNotifier: ViewControllerLifecycleNot
};

self.lifecycleEventDelegates.add(self.sheetPresentationStateMachine);
self.sheetLifecycleEventDelegates.add(self.sheetPresentationStateMachine);

presentationController.delegate = self;
self._didSetup = true;
Expand Down Expand Up @@ -313,6 +314,31 @@ extension ModalSheetViewControllerLifecycleNotifier: UIAdaptivePresentationContr
) {
self.presentationControllerDelegateProxy?
.presentationControllerWillDismiss?(presentationController);

#if DEBUG
if Self._debugShouldLogSheetEvents {
self.transitionCoordinator?.notifyWhenInteractionChanges { context in
print(
"ModalSheetViewControllerLifecycleNotifier.presentationControllerWillDismiss",
"\n - instance:", Unmanaged.passUnretained(self).toOpaque(),
"\n - className:", self.className,
"\n - transitionCoordinator.notifyWhenInteractionChanges",
"\n - isBeingDismissed: ", self.isBeingDismissed,
"\n - isBeingPresented: ", self.isBeingPresented,
"\n - context: ", context.debugDescription ?? "N/A",
"\n - context, isCancelled: ", context.isCancelled,
"\n - context, isAnimated: ", context.isAnimated,
"\n - context, isInteractive: ", context.isInteractive,
"\n - context, isInterruptible: ", context.isInterruptible,
"\n - context, initiallyInteractive: ", context.initiallyInteractive,
"\n - context, percentComplete: ", context.percentComplete,
"\n - context, transitionDuration: ", context.transitionDuration,
"\n",
"\n"
);
};
};
#endif

self.sheetLifecycleEventDelegates.invoke {
$0.notifyOnSheetWillDismissViaGesture(
Expand Down

0 comments on commit b08364a

Please sign in to comment.