From 5d26263f58b820558f0f8d88d1affa1685337096 Mon Sep 17 00:00:00 2001 From: Dominic Go <18517029+dominicstop@users.noreply.github.com> Date: Sun, 4 Jun 2023 22:38:58 +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`. --- .../AdaptiveModalEventNotifiable.swift | 26 +++++ .../AdaptiveModal/AdaptiveModalManager.swift | 103 +++++++++++++----- .../Test/RNIDraggableTestViewController.swift | 51 ++++++--- .../project.pbxproj | 4 + 4 files changed, 145 insertions(+), 39 deletions(-) create mode 100644 experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalEventNotifiable.swift diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalEventNotifiable.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalEventNotifiable.swift new file mode 100644 index 00000000..8906eba3 --- /dev/null +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalEventNotifiable.swift @@ -0,0 +1,26 @@ +// +// AdaptiveModalEventNotifiable.swift +// swift-programmatic-modal +// +// Created by Dominic Go on 6/4/23. +// + +import Foundation + + +protocol AdaptiveModalEventNotifiable: AnyObject { + + func notifyOnModalWillSnap( + prevSnapPointIndex: Int?, + nextSnapPointIndex: Int, + snapPointConfig: AdaptiveModalSnapPointConfig, + interpolationPoint: AdaptiveModalInterpolationPoint + ); + + func notifyOnModalDidSnap( + prevSnapPointIndex: Int?, + currentSnapPointIndex: Int, + snapPointConfig: AdaptiveModalSnapPointConfig, + interpolationPoint: AdaptiveModalInterpolationPoint + ); +}; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift index 36251c5c..cbf0c6d5 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift @@ -7,27 +7,28 @@ import UIKit - class AdaptiveModalManager { // MARK: - Properties - Config-Related // ------------------------------------ var modalConfig: AdaptiveModalConfig; - - var currentSnapPointIndex = 0; + var enableSnapping = true; - // MARK: - Properties - Refs/Providers - // ------------------------------------ - - var currentSizeProvider: () -> CGSize; + // MARK: - Properties - Refs + // -------------------------- + weak var eventDelegate: AdaptiveModalEventNotifiable?; + lazy var dummyModalView = UIView(); weak var targetView: UIView?; weak var modalView: UIView?; + var animator: UIViewPropertyAnimator?; + var displayLink: CADisplayLink?; + weak var modalBackgroundView: UIView?; weak var modalBackgroundVisualEffectView: UIVisualEffectView?; weak var backgroundDimmingView: UIView?; @@ -42,18 +43,28 @@ class AdaptiveModalManager { var prevModalFrame: CGRect = .zero; + var nextSnapPointIndex: Int?; + var backgroundVisualEffectAnimator: AdaptiveModalPropertyAnimator?; var modalBackgroundVisualEffectAnimator: AdaptiveModalPropertyAnimator?; // MARK: - Properties // ------------------- - var animator: UIViewPropertyAnimator?; - var displayLink: CADisplayLink?; + var currentSizeProvider: () -> CGSize; + + var prevSnapPointIndex: Int?; + var currentSnapPointIndex = 0 { + didSet { + self.prevSnapPointIndex = oldValue; + } + }; /// The computed frames of the modal based on the snap points var interpolationSteps: [AdaptiveModalInterpolationPoint]?; + var currentInterpolationIndex = 0; + // MARK: - Computed Properties // --------------------------- @@ -94,7 +105,7 @@ class AdaptiveModalManager { }; var currentInterpolationStep: AdaptiveModalInterpolationPoint? { - self.interpolationSteps?[self.currentSnapPointIndex]; + self.interpolationSteps?[self.currentInterpolationIndex]; }; var gestureInitialVelocity: CGVector? { @@ -679,14 +690,14 @@ class AdaptiveModalManager { // MARK: - Functions // ----------------- - func clearGestureValues(){ + private func clearGestureValues(){ self.gestureOffset = nil; self.gestureInitialPoint = nil; self.gestureVelocity = nil; self.gesturePoint = nil; }; - func clearAnimators(){ + private func clearAnimators(){ self.backgroundVisualEffectAnimator?.clear(); self.backgroundVisualEffectAnimator = nil; @@ -753,10 +764,8 @@ class AdaptiveModalManager { animator.startAnimation(); }; - func getClosestSnapPoint( - forGestureCoord gestureCoord: CGFloat - ) -> ( - snapPointIndex: Int, + func getClosestSnapPoint(forCoord coord: CGFloat? = nil) -> ( + interpolationIndex: Int, snapPointConfig: AdaptiveModalSnapPointConfig, interpolationPoint: AdaptiveModalInterpolationPoint )? { @@ -764,7 +773,8 @@ class AdaptiveModalManager { let modalView = self.modalView else { return nil }; - let inputCoord = modalView.frame.origin[keyPath: self.modalConfig.inputValueKeyForPoint]; + let inputCoord = coord ?? + modalView.frame.origin[keyPath: self.modalConfig.inputValueKeyForPoint]; let delta = interpolationSteps.map { abs($0.computedRect.origin[keyPath: self.modalConfig.inputValueKeyForPoint] - inputCoord); @@ -776,15 +786,18 @@ class AdaptiveModalManager { let closestSnapPoint = deltaSorted.first!; - let closestSnapPointIndex = min( + let closestInterpolationIndex = min( closestSnapPoint.offset, self.modalConfig.snapPointLastIndex ); + let interpolationPoint = interpolationSteps[closestInterpolationIndex]; + let snapPointIndex = interpolationPoint.snapPointIndex; + return ( - snapPointIndex: closestSnapPointIndex, - snapPointConfig: self.modalConfig.snapPoints[closestSnapPointIndex], - interpolationPoint: interpolationSteps[closestSnapPointIndex] + interpolationIndex: closestInterpolationIndex, + snapPointConfig: self.modalConfig.snapPoints[snapPointIndex], + interpolationPoint: interpolationPoint ); }; @@ -917,10 +930,10 @@ class AdaptiveModalManager { switch gesture.state { case .began: self.gestureInitialPoint = gesturePoint; - case .changed: self.applyInterpolationToModal(forGesturePoint: gesturePoint); + self.onModalWillSnap(); case .cancelled, .ended: guard self.enableSnapping else { @@ -934,7 +947,7 @@ class AdaptiveModalManager { gestureFinalPoint[keyPath: self.modalConfig.inputValueKeyForPoint]; let closestSnapPoint = - self.getClosestSnapPoint(forGestureCoord: gestureFinalCoord); + self.getClosestSnapPoint(forCoord: gestureFinalCoord); guard let closestSnapPoint = closestSnapPoint else { self.clearGestureValues(); @@ -942,13 +955,17 @@ class AdaptiveModalManager { }; self.animateModal(to: closestSnapPoint.interpolationPoint) { _ in - self.endDisplayLink(); + self.currentInterpolationIndex = + closestSnapPoint.interpolationIndex; + + self.currentSnapPointIndex = + closestSnapPoint.interpolationPoint.snapPointIndex; + self.endDisplayLink(); + self.onModalDidSnap(); }; self.startDisplayLink(); - - self.currentSnapPointIndex = closestSnapPoint.snapPointIndex; self.clearGestureValues(); default: @@ -991,4 +1008,38 @@ class AdaptiveModalManager { duration: interpolatedDuration ); }; + + // MARK: - Event Functions + // ----------------------- + + func onModalWillSnap(){ + guard let _ = self.modalView else { return }; + + guard let closestSnapPoint = self.getClosestSnapPoint() + else { return }; + + let prevIndex = self.nextSnapPointIndex; + let nextIndex = closestSnapPoint.interpolationPoint.snapPointIndex; + + guard prevIndex != nextIndex else { return }; + self.nextSnapPointIndex = nextIndex; + + self.eventDelegate?.notifyOnModalWillSnap( + prevSnapPointIndex: prevIndex, + nextSnapPointIndex: nextIndex, + snapPointConfig: closestSnapPoint.snapPointConfig, + interpolationPoint: closestSnapPoint.interpolationPoint + ); + }; + + func onModalDidSnap(){ + self.eventDelegate?.notifyOnModalDidSnap( + prevSnapPointIndex: self.prevSnapPointIndex, + currentSnapPointIndex: self.currentSnapPointIndex, + snapPointConfig: self.currentSnapPointConfig, + interpolationPoint: self.currentInterpolationStep! + ); + + self.nextSnapPointIndex = nil; + }; }; diff --git a/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift b/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift index 6e1705a6..cd4fbba3 100644 --- a/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift +++ b/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift @@ -216,7 +216,7 @@ enum AdaptiveModalConfigTestPresets: CaseIterable { ), animationKeyframe: AdaptiveModalAnimationConfig( modalBackgroundOpacity: 0.83, - modalCornerRadius: 15, + modalCornerRadius: 25, modalMaskedCorners: [ .layerMinXMinYCorner, .layerMaxXMinYCorner, @@ -243,18 +243,23 @@ enum AdaptiveModalConfigTestPresets: CaseIterable { class RNIDraggableTestViewController : UIViewController { - lazy var modalManager = AdaptiveModalManager( - modalConfig: AdaptiveModalConfigTestPresets.default.config, - modalView: self.floatingView, - targetView: self.view, - modalBackgroundView: self.modalBackgroundView, - modalBackgroundVisualEffectView: self.modalBackgroundVisualEffectView, - backgroundDimmingView: self.backgroundDimmingView, - backgroundVisualEffectView: self.backgroundVisualEffectView, - currentSizeProvider: { - .zero - } - ); + lazy var modalManager = { + let manager = AdaptiveModalManager( + modalConfig: AdaptiveModalConfigTestPresets.default.config, + modalView: self.floatingView, + targetView: self.view, + modalBackgroundView: self.modalBackgroundView, + modalBackgroundVisualEffectView: self.modalBackgroundVisualEffectView, + backgroundDimmingView: self.backgroundDimmingView, + backgroundVisualEffectView: self.backgroundVisualEffectView, + currentSizeProvider: { + .zero + } + ); + + manager.eventDelegate = self; + return manager; + }(); private var initialGesturePoint: CGPoint = .zero; private var floatingViewInitialCenter: CGPoint = .zero @@ -373,3 +378,23 @@ class RNIDraggableTestViewController : UIViewController { }; }; + +extension RNIDraggableTestViewController: AdaptiveModalEventNotifiable { + func notifyOnModalWillSnap( + prevSnapPointIndex: Int?, + nextSnapPointIndex: Int, + snapPointConfig: AdaptiveModalSnapPointConfig, + interpolationPoint: AdaptiveModalInterpolationPoint + ) { + self.floatingViewLabel.text = "\(nextSnapPointIndex)"; + } + + func notifyOnModalDidSnap( + prevSnapPointIndex: Int?, + currentSnapPointIndex: Int, + snapPointConfig: AdaptiveModalSnapPointConfig, + interpolationPoint: AdaptiveModalInterpolationPoint + ) { + self.floatingViewLabel.text = "\(currentSnapPointIndex)"; + }; +}; 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 ff3c3fc5..50b5636c 100644 --- a/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj +++ b/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 88C2F45C2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C2F45B2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift */; }; 88C2F45E2A278A9200DA7450 /* AdaptiveModalPropertyAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C2F45D2A278A9200DA7450 /* AdaptiveModalPropertyAnimator.swift */; }; 88C2F4602A2CA8CF00DA7450 /* RoundedViewTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C2F45F2A2CA8CF00DA7450 /* RoundedViewTestViewController.swift */; }; + 88C2F4622A2CD81F00DA7450 /* AdaptiveModalEventNotifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C2F4612A2CD81F00DA7450 /* AdaptiveModalEventNotifiable.swift */; }; 88D016602A14C86B004664D2 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D0165F2A14C86B004664D2 /* RootViewController.swift */; }; 88D0168D2A1730B1004664D2 /* RNILayoutTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D0168C2A1730B1004664D2 /* RNILayoutTestViewController.swift */; }; 88D0169E2A1B0DD3004664D2 /* RNIDraggableTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D0169D2A1B0DD3004664D2 /* RNIDraggableTestViewController.swift */; }; @@ -105,6 +106,7 @@ 88C2F45B2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalSnapPointPreset.swift; sourceTree = ""; }; 88C2F45D2A278A9200DA7450 /* AdaptiveModalPropertyAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalPropertyAnimator.swift; sourceTree = ""; }; 88C2F45F2A2CA8CF00DA7450 /* RoundedViewTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedViewTestViewController.swift; sourceTree = ""; }; + 88C2F4612A2CD81F00DA7450 /* AdaptiveModalEventNotifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalEventNotifiable.swift; sourceTree = ""; }; 88D0165F2A14C86B004664D2 /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; 88D0168C2A1730B1004664D2 /* RNILayoutTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNILayoutTestViewController.swift; sourceTree = ""; }; 88D0169D2A1B0DD3004664D2 /* RNIDraggableTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNIDraggableTestViewController.swift; sourceTree = ""; }; @@ -523,6 +525,7 @@ 88C2F45D2A278A9200DA7450 /* AdaptiveModalPropertyAnimator.swift */, 88D0188D2A1DCA61004664D2 /* AdaptiveModalManager.swift */, 88075E262A2121FE00B78388 /* AdaptiveModalManager+Helpers.swift */, + 88C2F4612A2CD81F00DA7450 /* AdaptiveModalEventNotifiable.swift */, ); path = AdaptiveModal; sourceTree = ""; @@ -658,6 +661,7 @@ 88D018882A1D0A36004664D2 /* AdaptiveModalConfig.swift in Sources */, 88D018432A1B3030004664D2 /* UIGestureRecognizer+Helpers.swift in Sources */, 88D0185B2A1B3030004664D2 /* RNIIdentifiable+Default.swift in Sources */, + 88C2F4622A2CD81F00DA7450 /* AdaptiveModalEventNotifiable.swift in Sources */, 88D018762A1B3030004664D2 /* RNIComputableSizeMode.swift in Sources */, 88D018262A1B3030004664D2 /* UIColor+Helpers.swift in Sources */, 88E8C0182A224A8D008C2FF8 /* AdaptiveModalSnapAnimationConfig.swift in Sources */,