From 7146cde7f3ce5caa3f5193a286e7173ff2c0f940 Mon Sep 17 00:00:00 2001 From: Dominic Go <18517029+dominicstop@users.noreply.github.com> Date: Thu, 15 Jun 2023 09:43:06 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=AB=20Update:=20Exp=20-=20`AdaptiveMod?= =?UTF-8?q?al`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Update experiment/test - `swift-programmatic-modal/AdaptiveModal`. --- .../AdaptiveModal/AdaptiveModalConfig.swift | 1 + ...IViewControllerTransitioningDelegate.swift | 2 - .../AdaptiveModal/AdaptiveModalManager.swift | 216 +++++++----- .../RNILayout/RNILayoutValueContext.swift | 1 - .../Test/AdaptiveModalConfigTestPresets.swift | 323 +++++++++++++++++ .../Test/RNIDraggableTestViewController.swift | 333 +----------------- .../project.pbxproj | 4 + 7 files changed, 474 insertions(+), 406 deletions(-) create mode 100644 experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift index cb44f9f1..c1d29774 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift @@ -36,6 +36,7 @@ struct AdaptiveModalConfig { let undershootSnapPoint: AdaptiveModalSnapPointPreset; let overshootSnapPoint: AdaptiveModalSnapPointPreset; + // the first snap point to snap to when the modal is first shown let initialSnapPointIndex: Int; // let entranceConfig: AdaptiveModalEntranceConfig; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift index 6f30c4a5..a9c1ce0c 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift @@ -7,8 +7,6 @@ import UIKit - - extension AdaptiveModalManager: UIViewControllerTransitioningDelegate { func animationController( diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift index f0870b56..99299dae 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift @@ -17,7 +17,7 @@ class AdaptiveModalManager: NSObject { var enableSnapping = true; var shouldSnapToOvershootSnapPoint = false; - var shouldSnapToInitialSnapPoint = false; + var shouldSnapToUnderShootSnapPoint = false; // MARK: - Properties - Layout-Related // ------------------------------------ @@ -189,9 +189,6 @@ class AdaptiveModalManager: NSObject { return CGPoint(x: nextX, y: nextY); }; - // MARK: - Properties - Flags - // --------------------------- - // MARK: - Properties // ------------------- @@ -225,11 +222,7 @@ class AdaptiveModalManager: NSObject { self.modalConfig = modalConfig; super.init(); - self.computeSnapPoints(); - self.setupViewControllers(); - self.setupInitViews(); - self.setupDummyModalView(); }; deinit { @@ -240,8 +233,10 @@ class AdaptiveModalManager: NSObject { // ------------------------- private func setupViewControllers() { - modalViewController?.modalPresentationStyle = .custom; - modalViewController?.transitioningDelegate = self; + guard let modalVC = self.modalViewController else { return }; + + modalVC.modalPresentationStyle = .custom; + modalVC.transitioningDelegate = self; }; private func setupInitViews(){ @@ -254,6 +249,8 @@ class AdaptiveModalManager: NSObject { private func setupGestureHandler(){ guard let modalView = self.modalView else { return }; + + modalView.gestureRecognizers?.removeAll(); modalView.addGestureRecognizer( UIPanGestureRecognizer( @@ -267,6 +264,9 @@ class AdaptiveModalManager: NSObject { guard let targetView = self.targetView else { return }; let dummyModalView = self.dummyModalView; + dummyModalView.removeAllAncestorConstraints(); + dummyModalView.removeFromSuperview(); + dummyModalView.backgroundColor = .clear; dummyModalView.alpha = 0.1; dummyModalView.isUserInteractionEnabled = false; @@ -854,8 +854,8 @@ class AdaptiveModalManager: NSObject { self.applyInterpolationToModal(forPoint: gestureInputPoint); }; - // MARK: - Functions - // ----------------- + // MARK: - Functions - Cleanup-Related + // ----------------------------------- private func clearGestureValues(){ self.gestureOffset = nil; @@ -870,6 +870,62 @@ class AdaptiveModalManager: NSObject { self.modalBackgroundVisualEffectAnimator?.clear(); self.modalBackgroundVisualEffectAnimator = nil; + + self.modalAnimator?.stopAnimation(true); + self.modalAnimator = nil; + }; + + private func cleanupViews(){ + let viewsToCleanup = [ + self.modalWrapperView, + self.modalView, + self.modalBackgroundView, + self.modalBackgroundVisualEffectView, + self.backgroundDimmingView, + self.backgroundVisualEffectView + ]; + + viewsToCleanup.forEach { + guard let view = $0, + view.superview != nil + else { return }; + + view.removeAllAncestorConstraints(); + view.removeFromSuperview(); + }; + }; + + private func cleanup(){ + self.clearGestureValues(); + self.clearAnimators(); + self.cleanupViews(); + }; + + // MARK: - Functions + // ----------------- + + private func computeSnapPoints( + usingLayoutValueContext context: RNILayoutValueContext? = nil + ) { + let context = context ?? self.layoutValueContext; + + self.rawInterpolationSteps = .Element.compute( + usingModalConfig: self.modalConfig, + layoutValueContext: context + ); + }; + + private func updateModal(){ + guard !self.isAnimating else { return }; + + if let gesturePoint = self.gesturePoint { + self.applyInterpolationToModal(forGesturePoint: gesturePoint); + + } else if self.currentInterpolationStep.computedRect != self.modalFrame { + self.applyInterpolationToModal( + forInputPercentValue: currentInterpolationStep.percent + ); + }; }; private func getClosestSnapPoint(forCoord coord: CGFloat? = nil) -> ( @@ -945,7 +1001,7 @@ class AdaptiveModalManager: NSObject { private func adjustInterpolationIndex(for nextIndex: Int) -> Int { if nextIndex == 0 { - return self.shouldSnapToInitialSnapPoint + return self.shouldSnapToUnderShootSnapPoint ? nextIndex : 1; }; @@ -1048,7 +1104,7 @@ class AdaptiveModalManager: NSObject { // MARK: - Functions - DisplayLink-Related // --------------------------------------- - func startDisplayLink() { + private func startDisplayLink() { let displayLink = CADisplayLink( target: self, selector: #selector(self.onDisplayLinkTick(displayLink:)) @@ -1062,11 +1118,11 @@ class AdaptiveModalManager: NSObject { displayLink.add(to: .current, forMode: .common); }; - func endDisplayLink() { + private func endDisplayLink() { self.displayLink?.invalidate(); }; - @objc func onDisplayLinkTick(displayLink: CADisplayLink) { + @objc private func onDisplayLinkTick(displayLink: CADisplayLink) { guard let dummyModalViewPresentationLayer = self.dummyModalView.layer.presentation(), let interpolationRangeMaxInput = self.interpolationRangeMaxInput @@ -1106,49 +1162,82 @@ class AdaptiveModalManager: NSObject { self.prevModalFrame = nextModalFrame; }; - // MARK: - User-Invoked Functions - // ------------------------------ + // MARK: - Event Functions + // ----------------------- - func computeSnapPoints( - usingLayoutValueContext context: RNILayoutValueContext? = nil - ) { - let context = context ?? self.layoutValueContext; + private func notifyOnModalWillSnap(){ + let interpolationSteps = self.interpolationSteps!; + let prevIndex = self.currentInterpolationIndex; - self.rawInterpolationSteps = .Element.compute( - usingModalConfig: self.modalConfig, - layoutValueContext: context + let nextIndex: Int = { + guard let nextIndex = self.nextInterpolationIndex else { + let closestSnapPoint = self.getClosestSnapPoint(); + return closestSnapPoint.interpolationPoint.snapPointIndex; + }; + + return nextIndex; + }(); + + let nextPoint = self.interpolationSteps[nextIndex]; + + guard prevIndex != nextIndex else { return }; + + self.eventDelegate?.notifyOnModalWillSnap( + prevSnapPointIndex: interpolationSteps[prevIndex].snapPointIndex, + nextSnapPointIndex: interpolationSteps[nextIndex].snapPointIndex, + snapPointConfig: self.modalConfig.snapPoints[nextPoint.snapPointIndex], + interpolationPoint: nextPoint + ); + }; + + private func notifyOnModalDidSnap(){ + self.eventDelegate?.notifyOnModalDidSnap( + prevSnapPointIndex: + interpolationSteps[self.prevInterpolationIndex].snapPointIndex, + + currentSnapPointIndex: + interpolationSteps[self.currentInterpolationIndex].snapPointIndex, + + snapPointConfig: self.currentSnapPointConfig, + interpolationPoint: self.currentInterpolationStep ); }; + // MARK: - User-Invoked Functions + // ------------------------------ + func prepareForPresentation( modalView: UIView, targetView: UIView ) { + let didViewsChange = + modalView !== self.modalView || targetView !== self.targetView; + + if didViewsChange { + self.cleanup(); + }; + self.modalView = modalView; self.targetView = targetView; - + self.computeSnapPoints(); - - self.setupInitViews(); - self.setupDummyModalView(); - self.setupGestureHandler(); + self.setupViewControllers(); - self.setupAddViews(); - self.setupViewConstraints(); + if didViewsChange { + self.setupInitViews(); + self.setupDummyModalView(); + self.setupGestureHandler(); + + self.setupAddViews(); + self.setupViewConstraints(); + }; + self.updateModal(); }; - func updateModal(){ - guard !self.isAnimating else { return }; - - if let gesturePoint = self.gesturePoint { - self.applyInterpolationToModal(forGesturePoint: gesturePoint); - - } else if self.currentInterpolationStep.computedRect != self.modalFrame { - self.applyInterpolationToModal( - forInputPercentValue: currentInterpolationStep.percent - ); - }; + func notifyDidLayoutSubviews(){ + self.computeSnapPoints(); + self.updateModal(); }; func snapTo( @@ -1227,45 +1316,4 @@ class AdaptiveModalManager: NSObject { let nextIndex = self.modalConfig.initialSnapPointIndex; self.snapTo(interpolationIndex: nextIndex, completion: completion); }; - - // MARK: - Event Functions - // ----------------------- - - private func notifyOnModalWillSnap(){ - let interpolationSteps = self.interpolationSteps!; - let prevIndex = self.currentInterpolationIndex; - - let nextIndex: Int = { - guard let nextIndex = self.nextInterpolationIndex else { - let closestSnapPoint = self.getClosestSnapPoint(); - return closestSnapPoint.interpolationPoint.snapPointIndex; - }; - - return nextIndex; - }(); - - let nextPoint = self.interpolationSteps[nextIndex]; - - guard prevIndex != nextIndex else { return }; - - self.eventDelegate?.notifyOnModalWillSnap( - prevSnapPointIndex: interpolationSteps[prevIndex].snapPointIndex, - nextSnapPointIndex: interpolationSteps[nextIndex].snapPointIndex, - snapPointConfig: self.modalConfig.snapPoints[nextPoint.snapPointIndex], - interpolationPoint: nextPoint - ); - }; - - private func notifyOnModalDidSnap(){ - self.eventDelegate?.notifyOnModalDidSnap( - prevSnapPointIndex: - interpolationSteps[self.prevInterpolationIndex].snapPointIndex, - - currentSnapPointIndex: - interpolationSteps[self.currentInterpolationIndex].snapPointIndex, - - snapPointConfig: self.currentSnapPointConfig, - interpolationPoint: self.currentInterpolationStep - ); - }; }; diff --git a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueContext.swift b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueContext.swift index 1092b0f1..34b9281c 100644 --- a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueContext.swift +++ b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueContext.swift @@ -70,7 +70,6 @@ extension RNILayoutValueContext { self.windowSize = targetView.window?.bounds.size; self.safeAreaInsets = targetView.window?.safeAreaInsets; - self.currentSize = currentSize; }; }; diff --git a/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift b/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift new file mode 100644 index 00000000..5f3cfd45 --- /dev/null +++ b/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift @@ -0,0 +1,323 @@ +// +// AdaptiveModalConfigTestPresets.swift +// swift-programmatic-modal +// +// Created by Dominic Go on 6/15/23. +// + +import UIKit + +enum AdaptiveModalConfigTestPresets: CaseIterable { + + static let `default`: Self = .test03; + + case testModalTransform01; + + case test01; + case test02; + case test03; + + var config: AdaptiveModalConfig { + switch self { + case .testModalTransform01: return AdaptiveModalConfig( + snapPoints: [ + // snap point - 0 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .percent(percentValue: 0.8) + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.2) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalRotation: 0.2, + modalScaleX: 0.5, + modalScaleY: 0.5, + modalTranslateX: -100, + modalTranslateY: 20 + ) + ), + + // snap point - 1 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .percent(percentValue: 0.8) + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.4) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalRotation: -0.2, + modalScaleX: 0.5, + modalScaleY: 1, + modalTranslateX: 0, + modalTranslateY: 0 + ) + ), + // snap point - 2 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .percent(percentValue: 0.8) + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.6) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + //modalRotation: 1, + modalScaleX: 1, + modalScaleY: 1 + //modalTranslateX: 0, + //modalTranslateY: 0 + ) + ), + ], + snapDirection: .bottomToTop, + overshootSnapPoint: AdaptiveModalSnapPointPreset( + snapPoint: .fitScreenVertically + ) + ); + + case .test01: return AdaptiveModalConfig( + snapPoints: [ + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .stretch + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.1) + ) + ) + ), + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .stretch + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.3) + ) + ) + ), + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .stretch + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.7) + ) + ) + ), + ], + snapDirection: .bottomToTop + ); + + case .test02: return AdaptiveModalConfig( + snapPoints: [ + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .stretch + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.3) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalCornerRadius: 15, + modalMaskedCorners: [ + .layerMinXMinYCorner, + .layerMaxXMinYCorner + ], + backgroundVisualEffect: UIBlurEffect(style: .regular), + backgroundVisualEffectIntensity: 0 + ) + ), + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .center, + width: RNILayoutValue( + mode: .percent(percentValue: 0.7), + maxValue: .constant(ScreenSize.iPhone8.size.width) + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.7), + maxValue: .constant(ScreenSize.iPhone8.size.height) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalCornerRadius: 20, + modalMaskedCorners: [ + .layerMinXMinYCorner, + .layerMinXMaxYCorner, + .layerMaxXMinYCorner, + .layerMaxXMaxYCorner + ], + backgroundVisualEffect: UIBlurEffect(style: .regular), + backgroundVisualEffectIntensity: 0.5 + ) + ), + ], + snapDirection: .bottomToTop, + interpolationClampingConfig: .init( + shouldClampModalLastHeight: true, + shouldClampModalLastWidth: true, + shouldClampModalLastX: true + ) + ); + + case .test03: return AdaptiveModalConfig( + snapPoints: [ + // Snap Point 1 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .stretch + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.3) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + //modalOpacity: 1, + modalBackgroundOpacity: 0.9, + modalCornerRadius: 15, + modalMaskedCorners: [ + .layerMinXMinYCorner, + .layerMaxXMinYCorner + ], + modalBackgroundVisualEffect: UIBlurEffect(style: .systemUltraThinMaterial), + modalBackgroundVisualEffectIntensity: 1, + + backgroundOpacity: 0, + backgroundVisualEffect: UIBlurEffect(style: .systemUltraThinMaterialDark), + backgroundVisualEffectIntensity: 0 + ) + ), + + // Snap Point 2 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .stretch + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.5) + ), + marginLeft: .constant(15), + marginRight: .constant(15), + marginBottom: .constant(15) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + //modalOpacity: 0.5, + //modalBackgroundColor: .red, + modalBackgroundOpacity: 0.85, + modalCornerRadius: 15, + modalMaskedCorners: [ + .layerMinXMinYCorner, + .layerMaxXMinYCorner, + .layerMinXMaxYCorner, + .layerMaxXMaxYCorner + ], + modalBackgroundVisualEffectIntensity: 0.6, + //backgroundColor: .red, + backgroundOpacity: 0.1, + backgroundVisualEffectIntensity: 0.075 + ) + ), + + // Snap Point 3 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .center, + width: RNILayoutValue( + mode: .percent(percentValue: 0.85), + maxValue: .constant(ScreenSize.iPhone8.size.width) + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.75), + maxValue: .constant(ScreenSize.iPhone8.size.height) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalBackgroundOpacity: 0.8, + modalCornerRadius: 20, + modalMaskedCorners: [ + .layerMinXMinYCorner, + .layerMinXMaxYCorner, + .layerMaxXMinYCorner, + .layerMaxXMaxYCorner + ], + modalBackgroundVisualEffectIntensity: 1, + backgroundOpacity: 0, + //backgroundVisualEffectOpacity: 0.5, + backgroundVisualEffectIntensity: 0.5 + ) + ), + + // Snap Point 4 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .stretch + ), + height: RNILayoutValue( + mode: .stretch + ), + marginTop: .safeAreaInsets(insetKey: \.top) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalBackgroundOpacity: 0.83, + modalCornerRadius: 25, + modalMaskedCorners: [ + .layerMinXMinYCorner, + .layerMaxXMinYCorner, + ], + modalBackgroundVisualEffectIntensity: 1, + backgroundVisualEffectIntensity: 1 + ) + ), + ], + snapDirection: .bottomToTop, + interpolationClampingConfig: .init( + shouldClampModalLastHeight: true, + shouldClampModalLastWidth: true, + shouldClampModalLastX: true + ), + overshootSnapPoint: AdaptiveModalSnapPointPreset( + snapPoint: .fitScreen + ) + ); + }; + }; +}; + diff --git a/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift b/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift index 4cf96113..e54f422f 100644 --- a/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift +++ b/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift @@ -8,326 +8,19 @@ import UIKit -enum AdaptiveModalConfigTestPresets: CaseIterable { - - static let `default`: Self = .test03; - - case testModalTransform01; - - case test01; - case test02; - case test03; - - var config: AdaptiveModalConfig { - switch self { - case .testModalTransform01: return AdaptiveModalConfig( - snapPoints: [ - // snap point - 0 - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .percent(percentValue: 0.8) - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.2) - ) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - modalRotation: 0.2, - modalScaleX: 0.5, - modalScaleY: 0.5, - modalTranslateX: -100, - modalTranslateY: 20 - ) - ), - - // snap point - 1 - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .percent(percentValue: 0.8) - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.4) - ) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - modalRotation: -0.2, - modalScaleX: 0.5, - modalScaleY: 1, - modalTranslateX: 0, - modalTranslateY: 0 - ) - ), - // snap point - 2 - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .percent(percentValue: 0.8) - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.6) - ) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - //modalRotation: 1, - modalScaleX: 1, - modalScaleY: 1 - //modalTranslateX: 0, - //modalTranslateY: 0 - ) - ), - ], - snapDirection: .bottomToTop, - overshootSnapPoint: AdaptiveModalSnapPointPreset( - snapPoint: .fitScreenVertically - ) - ); - - case .test01: return AdaptiveModalConfig( - snapPoints: [ - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .stretch - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.1) - ) - ) - ), - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .stretch - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.3) - ) - ) - ), - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .stretch - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.7) - ) - ) - ), - ], - snapDirection: .bottomToTop - ); - - case .test02: return AdaptiveModalConfig( - snapPoints: [ - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .stretch - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.3) - ) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - modalCornerRadius: 15, - modalMaskedCorners: [ - .layerMinXMinYCorner, - .layerMaxXMinYCorner - ], - backgroundVisualEffect: UIBlurEffect(style: .regular), - backgroundVisualEffectIntensity: 0 - ) - ), - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .center, - width: RNILayoutValue( - mode: .percent(percentValue: 0.7), - maxValue: .constant(ScreenSize.iPhone8.size.width) - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.7), - maxValue: .constant(ScreenSize.iPhone8.size.height) - ) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - modalCornerRadius: 20, - modalMaskedCorners: [ - .layerMinXMinYCorner, - .layerMinXMaxYCorner, - .layerMaxXMinYCorner, - .layerMaxXMaxYCorner - ], - backgroundVisualEffect: UIBlurEffect(style: .regular), - backgroundVisualEffectIntensity: 0.5 - ) - ), - ], - snapDirection: .bottomToTop, - interpolationClampingConfig: .init( - shouldClampModalLastHeight: true, - shouldClampModalLastWidth: true, - shouldClampModalLastX: true - ) - ); - - case .test03: return AdaptiveModalConfig( - snapPoints: [ - // Snap Point 1 - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .stretch - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.3) - ) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - //modalOpacity: 1, - modalBackgroundOpacity: 0.9, - modalCornerRadius: 15, - modalMaskedCorners: [ - .layerMinXMinYCorner, - .layerMaxXMinYCorner - ], - modalBackgroundVisualEffect: UIBlurEffect(style: .systemUltraThinMaterial), - modalBackgroundVisualEffectIntensity: 1, - - backgroundOpacity: 0, - backgroundVisualEffect: UIBlurEffect(style: .systemUltraThinMaterialDark), - backgroundVisualEffectIntensity: 0 - ) - ), - - // Snap Point 2 - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .stretch - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.5) - ), - marginLeft: .constant(15), - marginRight: .constant(15), - marginBottom: .constant(15) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - //modalOpacity: 0.5, - //modalBackgroundColor: .red, - modalBackgroundOpacity: 0.85, - modalCornerRadius: 15, - modalMaskedCorners: [ - .layerMinXMinYCorner, - .layerMaxXMinYCorner, - .layerMinXMaxYCorner, - .layerMaxXMaxYCorner - ], - modalBackgroundVisualEffectIntensity: 0.6, - //backgroundColor: .red, - backgroundOpacity: 0.1, - backgroundVisualEffectIntensity: 0.075 - ) - ), - - // Snap Point 3 - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .center, - width: RNILayoutValue( - mode: .percent(percentValue: 0.85), - maxValue: .constant(ScreenSize.iPhone8.size.width) - ), - height: RNILayoutValue( - mode: .percent(percentValue: 0.75), - maxValue: .constant(ScreenSize.iPhone8.size.height) - ) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - modalBackgroundOpacity: 0.8, - modalCornerRadius: 20, - modalMaskedCorners: [ - .layerMinXMinYCorner, - .layerMinXMaxYCorner, - .layerMaxXMinYCorner, - .layerMaxXMaxYCorner - ], - modalBackgroundVisualEffectIntensity: 1, - backgroundOpacity: 0, - //backgroundVisualEffectOpacity: 0.5, - backgroundVisualEffectIntensity: 0.5 - ) - ), - - // Snap Point 4 - AdaptiveModalSnapPointConfig( - snapPoint: RNILayout( - horizontalAlignment: .center, - verticalAlignment: .bottom, - width: RNILayoutValue( - mode: .stretch - ), - height: RNILayoutValue( - mode: .stretch - ), - marginTop: .safeAreaInsets(insetKey: \.top) - ), - animationKeyframe: AdaptiveModalAnimationConfig( - modalBackgroundOpacity: 0.83, - modalCornerRadius: 25, - modalMaskedCorners: [ - .layerMinXMinYCorner, - .layerMaxXMinYCorner, - ], - modalBackgroundVisualEffectIntensity: 1, - backgroundVisualEffectIntensity: 1 - ) - ), - ], - snapDirection: .bottomToTop, - interpolationClampingConfig: .init( - shouldClampModalLastHeight: true, - shouldClampModalLastWidth: true, - shouldClampModalLastX: true - ), - overshootSnapPoint: AdaptiveModalSnapPointPreset( - snapPoint: .fitScreen - ) - ); - }; - }; -}; - class RNIDraggableTestViewController : UIViewController { - lazy var modalManager = AdaptiveModalManager( - modalConfig: AdaptiveModalConfigTestPresets.default.config - ); + lazy var modalManager: AdaptiveModalManager = { + let manager = AdaptiveModalManager( + modalConfig: AdaptiveModalConfigTestPresets.default.config + ); + + manager.eventDelegate = self; + manager.shouldSnapToUnderShootSnapPoint = false; + + return manager; + }(); private var initialGesturePoint: CGPoint = .zero; private var floatingViewInitialCenter: CGPoint = .zero @@ -441,9 +134,11 @@ class RNIDraggableTestViewController : UIViewController { ]); }; + override func viewDidLayoutSubviews() { + self.modalManager.notifyDidLayoutSubviews(); + }; + @objc func onPressButtonPresentViewController(_ sender: UIButton){ - self.modalManager.eventDelegate = self; - self.modalManager.prepareForPresentation( modalView: self.floatingView, targetView: self.view diff --git a/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj b/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj index f5713512..f21e9ee9 100644 --- a/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj +++ b/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 886AFCAD2A3209D5004AC9FB /* RNILayoutValueMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886AFCAC2A3209D5004AC9FB /* RNILayoutValueMode.swift */; }; 886AFCAF2A320DED004AC9FB /* RNILayoutValuePercentTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886AFCAE2A320DED004AC9FB /* RNILayoutValuePercentTarget.swift */; }; 886AFCB12A325B6F004AC9FB /* RNILayoutValueContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886AFCB02A325B6F004AC9FB /* RNILayoutValueContext.swift */; }; + 88A2EF742A3A98F6006B5235 /* AdaptiveModalConfigTestPresets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88A2EF732A3A98F6006B5235 /* AdaptiveModalConfigTestPresets.swift */; }; 88B7D0EF29C593F400490628 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B7D0EE29C593F400490628 /* AppDelegate.swift */; }; 88B7D0F129C593F400490628 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B7D0F029C593F400490628 /* SceneDelegate.swift */; }; 88B7D0F829C593F600490628 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88B7D0F729C593F600490628 /* Assets.xcassets */; }; @@ -114,6 +115,7 @@ 886AFCAC2A3209D5004AC9FB /* RNILayoutValueMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNILayoutValueMode.swift; sourceTree = ""; }; 886AFCAE2A320DED004AC9FB /* RNILayoutValuePercentTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNILayoutValuePercentTarget.swift; sourceTree = ""; }; 886AFCB02A325B6F004AC9FB /* RNILayoutValueContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNILayoutValueContext.swift; sourceTree = ""; }; + 88A2EF732A3A98F6006B5235 /* AdaptiveModalConfigTestPresets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalConfigTestPresets.swift; sourceTree = ""; }; 88B7D0EB29C593F400490628 /* swift-programmatic-modal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "swift-programmatic-modal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 88B7D0EE29C593F400490628 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 88B7D0F029C593F400490628 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -258,6 +260,7 @@ 88D0168B2A173093004664D2 /* Test */ = { isa = PBXGroup; children = ( + 88A2EF732A3A98F6006B5235 /* AdaptiveModalConfigTestPresets.swift */, 88D0168C2A1730B1004664D2 /* RNILayoutTestViewController.swift */, 88D0169D2A1B0DD3004664D2 /* RNIDraggableTestViewController.swift */, 88D0187B2A1B32E6004664D2 /* TestRoutes.swift */, @@ -669,6 +672,7 @@ 88D018772A1B3030004664D2 /* RNIComputableValue.swift in Sources */, 884A18FA2A3146CA0044AA66 /* AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift in Sources */, 88C2F45C2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift in Sources */, + 88A2EF742A3A98F6006B5235 /* AdaptiveModalConfigTestPresets.swift in Sources */, 88E8C01C2A23203E008C2FF8 /* RNILayoutPreset.swift in Sources */, 88C2F4602A2CA8CF00DA7450 /* RoundedViewTestViewController.swift in Sources */, 88D018292A1B3030004664D2 /* RNICleanupMode.swift in Sources */,