diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift index 8fc0dd99..18315059 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift @@ -26,9 +26,15 @@ struct AdaptiveModalConfig { let snapAnimationConfig: AdaptiveModalSnapAnimationConfig; let interpolationClampingConfig: AdaptiveModalClampingConfig; + let overshootSnapPoint: AdaptiveModalSnapPointPreset; + // let entranceConfig: AdaptiveModalEntranceConfig; // let snapSwipeVelocityThreshold: CGFloat = 0; + var snapPointLastIndex: Int { + self.snapPoints.count - 1; + }; + // MARK: - Init // ------------ @@ -36,15 +42,22 @@ struct AdaptiveModalConfig { snapPoints: [AdaptiveModalSnapPointConfig], snapDirection: Direction, snapAnimationConfig: AdaptiveModalSnapAnimationConfig = .default, - interpolationClampingConfig: AdaptiveModalClampingConfig = .default + interpolationClampingConfig: AdaptiveModalClampingConfig = .default, + overshootSnapPoint: AdaptiveModalSnapPointPreset? = nil ) { self.snapPoints = snapPoints; self.snapDirection = snapDirection; self.snapAnimationConfig = snapAnimationConfig; self.interpolationClampingConfig = interpolationClampingConfig; + + self.overshootSnapPoint = overshootSnapPoint + ?? .getDefault(forDirection: snapDirection); }; + // MARK: - Functions + // ----------------- + func sortInterpolationSteps(_ array: [T]) -> [T] { switch self.snapDirection { case .bottomToTop, .rightToLeft: diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift index 38fa5e44..9c5a7c0a 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift @@ -19,16 +19,23 @@ struct AdaptiveModalInterpolationPoint { withTargetRect targetRect: CGRect, currentSize: CGSize, snapPointConfig: AdaptiveModalSnapPointConfig, - modalCornerRadius: CGFloat, - modalMaskedCorners: CACornerMask + prevSnapPointConfig: AdaptiveModalSnapPointConfig? = nil ) { self.computedRect = snapPointConfig.snapPoint.computeRect( withTargetRect: targetRect, currentSize: currentSize ); - self.modalCornerRadius = modalCornerRadius; - self.modalMaskedCorners = modalMaskedCorners; + let keyframeCurrent = snapPointConfig.animationKeyframe; + let keyframePrev = prevSnapPointConfig?.animationKeyframe; + + self.modalCornerRadius = keyframeCurrent?.modalCornerRadius + ?? keyframePrev?.modalCornerRadius + ?? Self.DefaultCornerRadius; + + self.modalMaskedCorners = keyframePrev?.modalMaskedCorners + ?? keyframePrev?.modalMaskedCorners + ?? Self.DefaultMaskedCorners; }; func apply(toModalView modalView: UIView){ @@ -39,44 +46,55 @@ struct AdaptiveModalInterpolationPoint { }; extension AdaptiveModalInterpolationPoint { + + private static let DefaultCornerRadius: CGFloat = 0; + + private static let DefaultMaskedCorners: CACornerMask = [ + .layerMaxXMinYCorner, + .layerMinXMinYCorner, + .layerMaxXMaxYCorner, + .layerMinXMaxYCorner, + ]; static func compute( usingModalConfig modalConfig: AdaptiveModalConfig, withTargetRect targetRect: CGRect, currentSize: CGSize - ) -> [AdaptiveModalInterpolationPoint] { + ) -> [Self] { var items: [AdaptiveModalInterpolationPoint] = []; - let defaultCornerRadius: CGFloat = 0; - - let defaultMaskedCorners: CACornerMask = [ - .layerMaxXMinYCorner, - .layerMinXMinYCorner, - .layerMaxXMaxYCorner, - .layerMinXMaxYCorner, - ]; - - for snapConfig in modalConfig.snapPoints { - let keyframe = snapConfig.animationKeyframe; - let prevKeyframe = items.last; + for (index, snapConfig) in modalConfig.snapPoints.enumerated() { + let prevSnapConfig = modalConfig.snapPoints[safeIndex: index]; items.append( AdaptiveModalInterpolationPoint( - withTargetRect : targetRect, - currentSize : currentSize, + withTargetRect: targetRect, + currentSize: currentSize, snapPointConfig: snapConfig, - - modalCornerRadius: keyframe?.modalCornerRadius - ?? prevKeyframe?.modalCornerRadius ?? defaultCornerRadius, - - modalMaskedCorners: keyframe?.modalMaskedCorners - ?? prevKeyframe?.modalMaskedCorners ?? defaultMaskedCorners + prevSnapPointConfig: prevSnapConfig ) ); }; + items.append({ + let prevSnapPointConfig = modalConfig.snapPoints.last!; + + let snapPointConfig = AdaptiveModalSnapPointConfig( + fromSnapPointPreset: modalConfig.overshootSnapPoint, + fromBaseLayoutConfig: prevSnapPointConfig.snapPoint, + withTargetRect: targetRect, + currentSize: currentSize + ); + + return AdaptiveModalInterpolationPoint( + withTargetRect: targetRect, + currentSize: currentSize, + snapPointConfig: snapPointConfig, + prevSnapPointConfig: prevSnapPointConfig + ); + }()); + return items; }; - }; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift index 2e91217a..b02abc72 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift @@ -270,19 +270,12 @@ class AdaptiveModalManager { modalBounds: modalView.bounds ); - print( - "applyInterpolationToModal" - + "\n - inputValue: \(inputValue)" - + "\n - nextModalRect: \(nextModalRect)" - + "\n - nextModalRadius: \(nextModalRadius)" - ); - if let nextModalRect = nextModalRect{ self.modalFrame = nextModalRect; }; if let nextModalRadius = nextModalRadius { - // modalView.layer.cornerRadius = modalCornerRadius; + modalView.layer.cornerRadius = nextModalRadius; }; }; @@ -399,7 +392,11 @@ class AdaptiveModalManager { }; let closestSnapPoint = deltaSorted.first!; - let closestSnapPointIndex = closestSnapPoint.offset; + + let closestSnapPointIndex = min( + closestSnapPoint.offset, + self.modalConfig.snapPointLastIndex + ); return ( snapPointIndex: closestSnapPointIndex, diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPoint.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPoint.swift index b618de0a..cc0d5fc4 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPoint.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPoint.swift @@ -17,5 +17,23 @@ struct AdaptiveModalSnapPointConfig { ) { self.snapPoint = snapPoint self.animationKeyframe = animationKeyframe - } + }; + + init( + fromSnapPointPreset snapPointPreset: AdaptiveModalSnapPointPreset, + fromBaseLayoutConfig baseLayoutConfig: RNILayout, + withTargetRect targetRect: CGRect, + currentSize: CGSize + ){ + let snapPointLayoutPreset = snapPointPreset.snapPointPreset; + + let snapPointLayout = snapPointLayoutPreset.getLayoutConfig( + fromBaseLayoutConfig: baseLayoutConfig, + withTargetRect: targetRect, + currentSize: currentSize + ); + + self.snapPoint = snapPointLayout; + self.animationKeyframe = snapPointPreset.animationKeyframe; + }; }; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPointPreset.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPointPreset.swift index 8e039763..d81dac93 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPointPreset.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPointPreset.swift @@ -1,89 +1,43 @@ // -// AdaptiveModalInitialSnapPoint.swift +// AdaptiveModalSnapPointPreset.swift // swift-programmatic-modal // -// Created by Dominic Go on 5/23/23. +// Created by Dominic Go on 5/31/23. // -import UIKit +import Foundation -enum AdaptiveModalSnapPointPreset { - case offscreenBottom, offscreenTop, offscreenLeft, offscreenRight; - case edgeBottom, edgeTop, edgeLeft, edgeRight; - case center; - - case layoutConfig(_ config: RNILayout); +struct AdaptiveModalSnapPointPreset { + + let snapPointPreset: RNILayoutPreset; + let animationKeyframe: AdaptiveModalAnimationConfig?; - func computeSnapPoint( - fromSnapPointConfig prevSnapPoint: RNILayout, - withTargetRect targetRect: CGRect, - currentSize: CGSize - ) -> RNILayout { + init( + snapPoint: RNILayoutPreset, + animationKeyframe: AdaptiveModalAnimationConfig? = nil + ) { + self.snapPointPreset = snapPoint; + self.animationKeyframe = animationKeyframe; + }; +}; + +extension AdaptiveModalSnapPointPreset { + static func getDefaultSnapPoint( + forDirection direction: AdaptiveModalConfig.Direction + ) -> RNILayoutPreset { + switch direction { + case .bottomToTop: return .offscreenTop; + case .topToBottom: return .offscreenBottom; + case .leftToRight: return .offscreenLeft; + case .rightToLeft: return .offscreenRight; + }; + }; - let prevRect = prevSnapPoint.computeRect( - withTargetRect: targetRect, - currentSize: currentSize + static func getDefault( + forDirection direction: AdaptiveModalConfig.Direction + ) -> Self { + Self.init( + snapPoint: Self.getDefaultSnapPoint(forDirection: direction) ); - - switch self { - case .offscreenBottom: - return .init( - derivedFrom: prevSnapPoint - ); - - case .offscreenTop: - return .init( - derivedFrom: prevSnapPoint, - verticalAlignment: .top, - marginTop: -prevRect.height - ); - - case .offscreenLeft: - return .init( - derivedFrom: prevSnapPoint, - horizontalAlignment: .left, - marginLeft: -prevRect.width - ); - - case .offscreenRight: - return .init( - derivedFrom: prevSnapPoint, - horizontalAlignment: .right, - marginRight: prevRect.width - ); - - case .edgeBottom: - return .init( - derivedFrom: prevSnapPoint, - verticalAlignment: .bottom - ); - - case .edgeTop: - return .init( - derivedFrom: prevSnapPoint, - verticalAlignment: .top - ); - - case .edgeLeft: - return .init( - derivedFrom: prevSnapPoint, - horizontalAlignment: .left - ); - - case .edgeRight: - return .init( - derivedFrom: prevSnapPoint, - horizontalAlignment: .right - ); - - case .center: - return .init( - derivedFrom: prevSnapPoint, - verticalAlignment: .center - ); - - case let .layoutConfig(config): - return config; - }; }; }; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/RNILayoutPreset.swift b/experiments/swift-programmatic-modal/AdaptiveModal/RNILayoutPreset.swift new file mode 100644 index 00000000..532bd96e --- /dev/null +++ b/experiments/swift-programmatic-modal/AdaptiveModal/RNILayoutPreset.swift @@ -0,0 +1,91 @@ +// +// RNILayoutPreset.swift +// swift-programmatic-modal +// +// Created by Dominic Go on 5/23/23. +// + +import UIKit + +enum RNILayoutPreset { + case offscreenBottom, offscreenTop, offscreenLeft, offscreenRight; + case edgeBottom, edgeTop, edgeLeft, edgeRight; + case center; + + case layoutConfig(_ config: RNILayout); + + func getLayoutConfig( + fromBaseLayoutConfig baseLayoutConfig: RNILayout, + withTargetRect targetRect: CGRect, + currentSize: CGSize + ) -> RNILayout { + + let baseRect = baseLayoutConfig.computeRect( + withTargetRect: targetRect, + currentSize: currentSize + ); + + switch self { + case .offscreenBottom: + return .init( + derivedFrom: baseLayoutConfig + ); + + case .offscreenTop: + return .init( + derivedFrom: baseLayoutConfig, + verticalAlignment: .top, + marginTop: -baseRect.height + ); + + case .offscreenLeft: + return .init( + derivedFrom: baseLayoutConfig, + horizontalAlignment: .left, + marginLeft: -baseRect.width + ); + + case .offscreenRight: + return .init( + derivedFrom: baseLayoutConfig, + horizontalAlignment: .right, + marginRight: baseRect.width + ); + + case .edgeBottom: + return .init( + derivedFrom: baseLayoutConfig, + verticalAlignment: .bottom + ); + + case .edgeTop: + return .init( + derivedFrom: baseLayoutConfig, + verticalAlignment: .top + ); + + case .edgeLeft: + return .init( + derivedFrom: baseLayoutConfig, + horizontalAlignment: .left + ); + + case .edgeRight: + return .init( + derivedFrom: baseLayoutConfig, + horizontalAlignment: .right + ); + + case .center: + return .init( + derivedFrom: baseLayoutConfig, + verticalAlignment: .center + ); + + case let .layoutConfig(config): + return config; + }; + }; +}; + + diff --git a/experiments/swift-programmatic-modal/Test/RNILayoutTestViewController.swift b/experiments/swift-programmatic-modal/Test/RNILayoutTestViewController.swift index 7fa7dd5a..0e53d286 100644 --- a/experiments/swift-programmatic-modal/Test/RNILayoutTestViewController.swift +++ b/experiments/swift-programmatic-modal/Test/RNILayoutTestViewController.swift @@ -290,8 +290,8 @@ class RNILayoutTestViewController : UIViewController { marginRight: 20, marginBottom: 20 ), - AdaptiveModalSnapPointPreset.offscreenTop.computeSnapPoint( - fromSnapPointConfig: RNILayout( + RNILayoutPreset.offscreenTop.getLayoutConfig( + fromBaseLayoutConfig: RNILayout( horizontalAlignment: .left, verticalAlignment: .top, width: RNIComputableValue( 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 8e4c530b..91d19cb8 100644 --- a/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj +++ b/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 88B7D0F129C593F400490628 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B7D0F029C593F400490628 /* SceneDelegate.swift */; }; 88B7D0F829C593F600490628 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88B7D0F729C593F600490628 /* Assets.xcassets */; }; 88B7D0FB29C593F600490628 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88B7D0F929C593F600490628 /* LaunchScreen.storyboard */; }; + 88C2F45C2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C2F45B2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.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 */; }; @@ -85,7 +86,7 @@ 88D0188E2A1DCA61004664D2 /* AdaptiveModalManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D0188D2A1DCA61004664D2 /* AdaptiveModalManager.swift */; }; 88E8C0182A224A8D008C2FF8 /* AdaptiveModalSnapAnimationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E8C0172A224A8D008C2FF8 /* AdaptiveModalSnapAnimationConfig.swift */; }; 88E8C01A2A228289008C2FF8 /* AdaptiveModalClampingConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E8C0192A228289008C2FF8 /* AdaptiveModalClampingConfig.swift */; }; - 88E8C01C2A23203E008C2FF8 /* AdaptiveModalSnapPointPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E8C01B2A23203E008C2FF8 /* AdaptiveModalSnapPointPreset.swift */; }; + 88E8C01C2A23203E008C2FF8 /* RNILayoutPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E8C01B2A23203E008C2FF8 /* RNILayoutPreset.swift */; }; 88E8C01E2A234B0A008C2FF8 /* UIBezierPath+VariadicCornerRadius.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E8C01D2A234B0A008C2FF8 /* UIBezierPath+VariadicCornerRadius.swift */; }; /* End PBXBuildFile section */ @@ -99,6 +100,7 @@ 88B7D0F729C593F600490628 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 88B7D0FA29C593F600490628 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 88B7D0FC29C593F600490628 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 88C2F45B2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalSnapPointPreset.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 = ""; }; @@ -172,7 +174,7 @@ 88D0188D2A1DCA61004664D2 /* AdaptiveModalManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalManager.swift; sourceTree = ""; }; 88E8C0172A224A8D008C2FF8 /* AdaptiveModalSnapAnimationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalSnapAnimationConfig.swift; sourceTree = ""; }; 88E8C0192A228289008C2FF8 /* AdaptiveModalClampingConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalClampingConfig.swift; sourceTree = ""; }; - 88E8C01B2A23203E008C2FF8 /* AdaptiveModalSnapPointPreset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveModalSnapPointPreset.swift; sourceTree = ""; }; + 88E8C01B2A23203E008C2FF8 /* RNILayoutPreset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNILayoutPreset.swift; sourceTree = ""; }; 88E8C01D2A234B0A008C2FF8 /* UIBezierPath+VariadicCornerRadius.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+VariadicCornerRadius.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -503,17 +505,18 @@ 88D018802A1D06EA004664D2 /* AdaptiveModal */ = { isa = PBXGroup; children = ( - 88E8C01B2A23203E008C2FF8 /* AdaptiveModalSnapPointPreset.swift */, + 88E8C01B2A23203E008C2FF8 /* RNILayoutPreset.swift */, + 88E8C01D2A234B0A008C2FF8 /* UIBezierPath+VariadicCornerRadius.swift */, 88D018812A1D09DD004664D2 /* AdaptiveModalAnimationConfig.swift */, 88D018852A1D0A1D004664D2 /* AdaptiveModalSnapPoint.swift */, + 88C2F45B2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift */, 88D0188B2A1DB02E004664D2 /* AdaptiveModalEntranceConfig.swift */, - 88D018872A1D0A36004664D2 /* AdaptiveModalConfig.swift */, - 88D0188D2A1DCA61004664D2 /* AdaptiveModalManager.swift */, - 88075E262A2121FE00B78388 /* AdaptiveModalManager+Helpers.swift */, 88E8C0172A224A8D008C2FF8 /* AdaptiveModalSnapAnimationConfig.swift */, 88E8C0192A228289008C2FF8 /* AdaptiveModalClampingConfig.swift */, - 88E8C01D2A234B0A008C2FF8 /* UIBezierPath+VariadicCornerRadius.swift */, + 88D018872A1D0A36004664D2 /* AdaptiveModalConfig.swift */, 880492572A23F89000D74E9F /* AdaptiveModalInterpolationPoint.swift */, + 88D0188D2A1DCA61004664D2 /* AdaptiveModalManager.swift */, + 88075E262A2121FE00B78388 /* AdaptiveModalManager+Helpers.swift */, ); path = AdaptiveModal; sourceTree = ""; @@ -624,7 +627,8 @@ 88D0183B2A1B3030004664D2 /* Collection+Helpers.swift in Sources */, 88D0188E2A1DCA61004664D2 /* AdaptiveModalManager.swift in Sources */, 88D018772A1B3030004664D2 /* RNIComputableValue.swift in Sources */, - 88E8C01C2A23203E008C2FF8 /* AdaptiveModalSnapPointPreset.swift in Sources */, + 88C2F45C2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift in Sources */, + 88E8C01C2A23203E008C2FF8 /* RNILayoutPreset.swift in Sources */, 88D018292A1B3030004664D2 /* RNICleanupMode.swift in Sources */, 88D018532A1B3030004664D2 /* RNIMulticastDelegate.swift in Sources */, 88D018642A1B3030004664D2 /* RNIViewControllerLifeCycleNotifiable.swift in Sources */,