diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalAnimationConfig.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalAnimationConfig.swift index c4d80178..56d629b6 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalAnimationConfig.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalAnimationConfig.swift @@ -7,43 +7,42 @@ import UIKit - -struct AdaptiveModalAnimationConfig { - var modalRotation: CGFloat?; +public struct AdaptiveModalAnimationConfig { + public var modalRotation: CGFloat?; - var modalScaleX: CGFloat?; - var modalScaleY: CGFloat?; + public var modalScaleX: CGFloat?; + public var modalScaleY: CGFloat?; - var modalTranslateX: CGFloat?; - var modalTranslateY: CGFloat?; + public var modalTranslateX: CGFloat?; + public var modalTranslateY: CGFloat?; - var modalBorderWidth: CGFloat?; - var modalBorderColor: UIColor?; + public var modalBorderWidth: CGFloat?; + public var modalBorderColor: UIColor?; - var modalShadowColor: UIColor?; - var modalShadowOffset: CGSize?; - var modalShadowOpacity: CGFloat?; - var modalShadowRadius: CGFloat?; + public var modalShadowColor: UIColor?; + public var modalShadowOffset: CGSize?; + public var modalShadowOpacity: CGFloat?; + public var modalShadowRadius: CGFloat?; - var modalCornerRadius: CGFloat?; - var modalMaskedCorners: CACornerMask?; + public var modalCornerRadius: CGFloat?; + public var modalMaskedCorners: CACornerMask?; - var modalOpacity: CGFloat?; - var modalBackgroundColor: UIColor?; - var modalBackgroundOpacity: CGFloat?; + public var modalOpacity: CGFloat?; + public var modalBackgroundColor: UIColor?; + public var modalBackgroundOpacity: CGFloat?; - var modalBackgroundVisualEffect: UIVisualEffect?; - var modalBackgroundVisualEffectOpacity: CGFloat?; - var modalBackgroundVisualEffectIntensity: CGFloat?; + public var modalBackgroundVisualEffect: UIVisualEffect?; + public var modalBackgroundVisualEffectOpacity: CGFloat?; + public var modalBackgroundVisualEffectIntensity: CGFloat?; - var backgroundColor: UIColor?; - var backgroundOpacity: CGFloat?; + public var backgroundColor: UIColor?; + public var backgroundOpacity: CGFloat?; - var backgroundVisualEffect: UIVisualEffect?; - var backgroundVisualEffectOpacity: CGFloat?; - var backgroundVisualEffectIntensity: CGFloat?; + public var backgroundVisualEffect: UIVisualEffect?; + public var backgroundVisualEffectOpacity: CGFloat?; + public var backgroundVisualEffectIntensity: CGFloat?; - init( + public init( modalRotation: CGFloat? = nil, modalScaleX: CGFloat? = nil, modalScaleY: CGFloat? = nil, diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalClampingConfig.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalClampingConfig.swift index 66437fa7..f7bfdda1 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalClampingConfig.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalClampingConfig.swift @@ -7,37 +7,37 @@ import Foundation -struct AdaptiveModalClampingConfig { - static let `default`: Self = .init(); +public struct AdaptiveModalClampingConfig { + public static let `default`: Self = .init(); - let shouldClampModalInitHeight: Bool; - let shouldClampModalLastHeight: Bool; + public let shouldClampModalInitHeight: Bool; + public let shouldClampModalLastHeight: Bool; - let shouldClampModalInitWidth: Bool; - let shouldClampModalLastWidth: Bool; + public let shouldClampModalInitWidth: Bool; + public let shouldClampModalLastWidth: Bool; - let shouldClampModalInitX: Bool; - let shouldClampModalLastX: Bool; + public let shouldClampModalInitX: Bool; + public let shouldClampModalLastX: Bool; - let shouldClampModalInitY: Bool; - let shouldClampModalLastY: Bool; + public let shouldClampModalInitY: Bool; + public let shouldClampModalLastY: Bool; - let shouldClampModalInitRotation: Bool; - let shouldClampModalLastRotation: Bool; + public let shouldClampModalInitRotation: Bool; + public let shouldClampModalLastRotation: Bool; - let shouldClampModalInitScaleX: Bool; - let shouldClampModalLastScaleX: Bool; + public let shouldClampModalInitScaleX: Bool; + public let shouldClampModalLastScaleX: Bool; - let shouldClampModalInitScaleY: Bool; - let shouldClampModalLastScaleY: Bool; + public let shouldClampModalInitScaleY: Bool; + public let shouldClampModalLastScaleY: Bool; - let shouldClampModalInitTranslateX: Bool; - let shouldClampModalLastTranslateX: Bool; + public let shouldClampModalInitTranslateX: Bool; + public let shouldClampModalLastTranslateX: Bool; - let shouldClampModalInitTranslateY: Bool; - let shouldClampModalLastTranslateY: Bool; + public let shouldClampModalInitTranslateY: Bool; + public let shouldClampModalLastTranslateY: Bool; - init( + public init( shouldClampModalInitHeight: Bool = false, shouldClampModalLastHeight: Bool = false, shouldClampModalInitWidth: Bool = false, diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift index f8986c35..c8d3251d 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalConfig.swift @@ -7,15 +7,19 @@ import UIKit -struct AdaptiveModalConfig { - enum Direction { +public struct AdaptiveModalConfig { + + // MARK: - Types + // ------------- + + public enum Direction { case bottomToTop; case topToBottom; case leftToRight; case rightToLeft; }; - enum SnapPercentStrategy { + public enum SnapPercentStrategy { case index; case position; }; @@ -23,19 +27,19 @@ struct AdaptiveModalConfig { // MARK: - Properties // ------------------ - let baseSnapPoints: [AdaptiveModalSnapPointConfig]; - let snapDirection: Direction; + public let baseSnapPoints: [AdaptiveModalSnapPointConfig]; + public let snapDirection: Direction; - let snapPercentStrategy: SnapPercentStrategy; + public let snapPercentStrategy: SnapPercentStrategy; - let snapAnimationConfig: AdaptiveModalSnapAnimationConfig; - let interpolationClampingConfig: AdaptiveModalClampingConfig; + public let snapAnimationConfig: AdaptiveModalSnapAnimationConfig; + public let interpolationClampingConfig: AdaptiveModalClampingConfig; - let undershootSnapPoint: AdaptiveModalSnapPointPreset; - let overshootSnapPoint: AdaptiveModalSnapPointPreset; + public let undershootSnapPoint: AdaptiveModalSnapPointPreset; + public let overshootSnapPoint: AdaptiveModalSnapPointPreset; // the first snap point to snap to when the modal is first shown - let initialSnapPointIndex: Int; + public let initialSnapPointIndex: Int; // let entranceConfig: AdaptiveModalEntranceConfig; // let snapSwipeVelocityThreshold: CGFloat = 0; @@ -43,7 +47,7 @@ struct AdaptiveModalConfig { // MARK: - Computed Properties // --------------------------- - var snapPoints: [AdaptiveModalSnapPointConfig] { + public var snapPoints: [AdaptiveModalSnapPointConfig] { .Element.deriveSnapPoints( undershootSnapPoint: self.undershootSnapPoint, inBetweenSnapPoints: self.baseSnapPoints, @@ -51,21 +55,21 @@ struct AdaptiveModalConfig { ); }; - var overshootSnapPointIndex: Int { + public var overshootSnapPointIndex: Int { self.snapPoints.count - 1; }; /// Defines which axis of the gesture point to use to drive the interpolation /// of the modal snap points /// - var inputValueKeyForPoint: KeyPath { + public var inputValueKeyForPoint: KeyPath { switch self.snapDirection { case .topToBottom, .bottomToTop: return \.y; case .leftToRight, .rightToLeft: return \.x; }; }; - var inputValueKeyForRect: KeyPath { + public var inputValueKeyForRect: KeyPath { switch self.snapDirection { case .bottomToTop: return \.minY; case .topToBottom: return \.maxY; @@ -74,14 +78,14 @@ struct AdaptiveModalConfig { }; }; - var maxInputRangeKeyForRect: KeyPath { + public var maxInputRangeKeyForRect: KeyPath { switch self.snapDirection { case .bottomToTop, .topToBottom: return \.height; case .leftToRight, .rightToLeft: return \.width; }; }; - var shouldInvertPercent: Bool { + public var shouldInvertPercent: Bool { switch self.snapDirection { case .bottomToTop, .rightToLeft: return true; default: return false; @@ -91,7 +95,7 @@ struct AdaptiveModalConfig { // MARK: - Init // ------------ - init( + public init( snapPoints: [AdaptiveModalSnapPointConfig], snapDirection: Direction, snapPercentStrategy: SnapPercentStrategy = .position, @@ -121,7 +125,7 @@ struct AdaptiveModalConfig { // MARK: - Functions // ----------------- - func sortInterpolationSteps(_ array: [T]) -> [T] { + public func sortInterpolationSteps(_ array: [T]) -> [T] { switch self.snapDirection { case .bottomToTop, .leftToRight: return array; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalEventNotifiable.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalEventNotifiable.swift index 8906eba3..296275ea 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalEventNotifiable.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalEventNotifiable.swift @@ -8,7 +8,7 @@ import Foundation -protocol AdaptiveModalEventNotifiable: AnyObject { +public protocol AdaptiveModalEventNotifiable: AnyObject { func notifyOnModalWillSnap( prevSnapPointIndex: Int?, diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift index 5a7964a5..55ede9fc 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift @@ -7,65 +7,58 @@ import UIKit -struct AdaptiveModalInterpolationPoint: Equatable { - - static let DefaultMaskedCorners: CACornerMask = [ - .layerMaxXMinYCorner, - .layerMinXMinYCorner, - .layerMaxXMaxYCorner, - .layerMinXMaxYCorner, - ]; +public struct AdaptiveModalInterpolationPoint: Equatable { // MARK: - Properties // ------------------ - var percent: CGFloat; - var snapPointIndex: Int; + public var percent: CGFloat; + public var snapPointIndex: Int; /// The computed frames of the modal based on the snap points - var computedRect: CGRect; + public var computedRect: CGRect; // MARK: - Properties - Keyframes // ------------------------------ - var modalRotation: CGFloat; + public var modalRotation: CGFloat; - var modalScaleX: CGFloat; - var modalScaleY: CGFloat; + public var modalScaleX: CGFloat; + public var modalScaleY: CGFloat; - var modalTranslateX: CGFloat; - var modalTranslateY: CGFloat; + public var modalTranslateX: CGFloat; + public var modalTranslateY: CGFloat; - var modalBorderWidth: CGFloat; - var modalBorderColor: UIColor; + public var modalBorderWidth: CGFloat; + public var modalBorderColor: UIColor; - var modalShadowColor: UIColor; - var modalShadowOffset: CGSize; - var modalShadowOpacity: CGFloat; - var modalShadowRadius: CGFloat; + public var modalShadowColor: UIColor; + public var modalShadowOffset: CGSize; + public var modalShadowOpacity: CGFloat; + public var modalShadowRadius: CGFloat; - var modalCornerRadius: CGFloat; - var modalMaskedCorners: CACornerMask; + public var modalCornerRadius: CGFloat; + public var modalMaskedCorners: CACornerMask; - var modalOpacity: CGFloat; - var modalBackgroundColor: UIColor; - var modalBackgroundOpacity: CGFloat; + public var modalOpacity: CGFloat; + public var modalBackgroundColor: UIColor; + public var modalBackgroundOpacity: CGFloat; - var modalBackgroundVisualEffect: UIVisualEffect?; - var modalBackgroundVisualEffectOpacity: CGFloat; - var modalBackgroundVisualEffectIntensity: CGFloat; + public var modalBackgroundVisualEffect: UIVisualEffect?; + public var modalBackgroundVisualEffectOpacity: CGFloat; + public var modalBackgroundVisualEffectIntensity: CGFloat; - var backgroundColor: UIColor; - var backgroundOpacity: CGFloat; + public var backgroundColor: UIColor; + public var backgroundOpacity: CGFloat; - var backgroundVisualEffect: UIVisualEffect?; - var backgroundVisualEffectOpacity: CGFloat; - var backgroundVisualEffectIntensity: CGFloat; + public var backgroundVisualEffect: UIVisualEffect?; + public var backgroundVisualEffectOpacity: CGFloat; + public var backgroundVisualEffectIntensity: CGFloat; // MARK: - Computed Properties // --------------------------- - var modalTransforms: [CGAffineTransform] { + public var modalTransforms: [CGAffineTransform] { var transforms: [CGAffineTransform] = []; transforms.append( @@ -83,7 +76,7 @@ struct AdaptiveModalInterpolationPoint: Equatable { return transforms; }; - var modalTransform: CGAffineTransform { + public var modalTransform: CGAffineTransform { self.modalTransforms.reduce(.identity){ $0.concatenating($1); }; @@ -133,66 +126,66 @@ struct AdaptiveModalInterpolationPoint: Equatable { }; }; - func apply(toModalView modalView: UIView){ + func apply( + toModalView modalView: UIView, + toModalWrapperView modalWrapperView: UIView, + toModalWrapperTransformView modalWrapperTransformView: UIView?, + toModalWrapperShadowView modalWrapperShadowView: UIView?, + toDummyModalView dummyModalView: UIView, + toModalBackgroundView modalBgView: UIView?, + toBackgroundView bgView: UIView?, + toModalBackgroundEffectView modalBgEffectView: UIVisualEffectView?, + toBackgroundVisualEffectView bgVisualEffectView: UIVisualEffectView? + ){ modalView.alpha = self.modalOpacity; modalView.layer.cornerRadius = self.modalCornerRadius; modalView.layer.maskedCorners = self.modalMaskedCorners; - }; - - func apply(toModalWrapperView modalWrapperView: UIView){ + modalWrapperView.frame = self.computedRect; - }; - - func apply(toModalWrapperTransformView view: UIView?){ - view?.transform = self.modalTransform; - }; - - func apply(toModalWrapperShadowView view: UIView?){ - guard let view = view else { return }; - // border - view.layer.borderWidth = self.modalBorderWidth; - view.layer.borderColor = self.modalBorderColor.cgColor; + if let view = modalWrapperTransformView { + view.transform = self.modalTransform; + }; + + if let view = modalWrapperShadowView { + // border + view.layer.borderWidth = self.modalBorderWidth; + view.layer.borderColor = self.modalBorderColor.cgColor; - // shadow - view.layer.shadowColor = self.modalShadowColor.cgColor; - view.layer.shadowOffset = self.modalShadowOffset; - view.layer.shadowOpacity = Float(self.modalShadowOpacity); - view.layer.shadowRadius = self.modalShadowRadius; - }; - - func apply(toDummyModalView dummyModalView: UIView){ + // shadow + view.layer.shadowColor = self.modalShadowColor.cgColor; + view.layer.shadowOffset = self.modalShadowOffset; + view.layer.shadowOpacity = Float(self.modalShadowOpacity); + view.layer.shadowRadius = self.modalShadowRadius; + }; + dummyModalView.frame = self.computedRect; - }; - - func apply(toModalBackgroundView modalBgView: UIView?){ - guard let modalBgView = modalBgView else { return }; - modalBgView.alpha = self.modalBackgroundOpacity; - modalBgView.backgroundColor = self.modalBackgroundColor; - }; - - func apply(toModalBackgroundEffectView effectView: UIVisualEffectView?){ - effectView?.alpha = self.modalBackgroundVisualEffectOpacity; - }; - - func apply(toBackgroundView bgView: UIView?){ - guard let bgView = bgView else { return }; + if let view = modalBgView { + view.alpha = self.modalBackgroundOpacity; + view.backgroundColor = self.modalBackgroundColor; + }; - bgView.alpha = self.backgroundOpacity; - bgView.backgroundColor = self.backgroundColor; - }; - - func apply(toBackgroundVisualEffectView effectView: UIView?){ - effectView?.alpha = self.backgroundVisualEffectOpacity; + if let bgView = bgView { + bgView.alpha = self.backgroundOpacity; + bgView.backgroundColor = self.backgroundColor; + }; + + if let effectView = modalBgEffectView { + effectView.alpha = self.modalBackgroundVisualEffectOpacity; + }; + + if let effectView = bgVisualEffectView { + effectView.alpha = self.backgroundVisualEffectOpacity; + }; }; }; // MARK: - Init // ------------ -extension AdaptiveModalInterpolationPoint { +public extension AdaptiveModalInterpolationPoint { init( usingModalConfig modalConfig: AdaptiveModalConfig, @@ -286,7 +279,7 @@ extension AdaptiveModalInterpolationPoint { self.modalMaskedCorners = keyframeCurrent?.modalMaskedCorners ?? keyframePrev?.modalMaskedCorners - ?? Self.DefaultMaskedCorners; + ?? .allCorners; self.modalOpacity = keyframeCurrent?.modalOpacity ?? keyframePrev?.modalOpacity @@ -335,7 +328,7 @@ extension AdaptiveModalInterpolationPoint { // MARK: - Helpers // --------------- -extension AdaptiveModalInterpolationPoint { +public extension AdaptiveModalInterpolationPoint { static func compute( usingModalConfig modalConfig: AdaptiveModalConfig, diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalKeyframePropertyAnimator.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalKeyframePropertyAnimator.swift index e7a1d98c..d4e003a8 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalKeyframePropertyAnimator.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalKeyframePropertyAnimator.swift @@ -8,13 +8,13 @@ import UIKit -struct AdaptiveModalKeyframePropertyAnimator { +public struct AdaptiveModalKeyframePropertyAnimator { - var animator: UIViewPropertyAnimator; + public var animator: UIViewPropertyAnimator; private weak var component: UIView?; - init( + public init( interpolationPoints: [AdaptiveModalInterpolationPoint], forComponent component: T, animation: @escaping ( @@ -46,11 +46,11 @@ struct AdaptiveModalKeyframePropertyAnimator { }; }; - func setFractionComplete(forPercent percent: CGFloat) { + public func setFractionComplete(forPercent percent: CGFloat) { self.animator.fractionComplete = 0; }; - func clear(){ + public func clear(){ self.animator.stopAnimation(true); }; }; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerAnimatedTransitioning.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerAnimatedTransitioning.swift index 8ed95df9..6605daa2 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerAnimatedTransitioning.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerAnimatedTransitioning.swift @@ -9,14 +9,14 @@ import UIKit extension AdaptiveModalManager: UIViewControllerAnimatedTransitioning { - func transitionDuration( + public func transitionDuration( using transitionContext: UIViewControllerContextTransitioning? ) -> TimeInterval { return self.modalConfig.snapAnimationConfig.springAnimationSettlingTime; }; - func animateTransition( + public func animateTransition( using transitionContext: UIViewControllerContextTransitioning ) { guard let fromVC = transitionContext.viewController(forKey: .from) diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift index 14ac28ee..5d9cb65a 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift @@ -8,7 +8,8 @@ import UIKit extension AdaptiveModalManager: UIAdaptivePresentationControllerDelegate { - func adaptivePresentationStyle( + + public func adaptivePresentationStyle( for controller: UIPresentationController, traitCollection: UITraitCollection ) -> UIModalPresentationStyle { @@ -20,7 +21,7 @@ extension AdaptiveModalManager: UIAdaptivePresentationControllerDelegate { extension AdaptiveModalManager: UIViewControllerTransitioningDelegate { - func presentationController( + public func presentationController( forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController @@ -36,7 +37,7 @@ extension AdaptiveModalManager: UIViewControllerTransitioningDelegate { return presentationController; }; - func animationController( + public func animationController( forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController @@ -46,7 +47,7 @@ extension AdaptiveModalManager: UIViewControllerTransitioningDelegate { return self; }; - func animationController( + public func animationController( forDismissed dismissed: UIViewController ) -> UIViewControllerAnimatedTransitioning? { diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift index 0dd52723..6d50f7b8 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift @@ -7,49 +7,49 @@ import UIKit -class AdaptiveModalManager: NSObject { +public class AdaptiveModalManager: NSObject { - enum PresentationState { + public enum PresentationState { case presenting, dismissing, none; }; // MARK: - Properties - Config-Related // ------------------------------------ - var modalConfig: AdaptiveModalConfig; + public var modalConfig: AdaptiveModalConfig; - var enableSnapping = true; - var enableOverShooting = true; + public var enableSnapping = true; + public var enableOverShooting = true; - var shouldSnapToUnderShootSnapPoint = true; - var shouldSnapToOvershootSnapPoint = false; + public var shouldSnapToUnderShootSnapPoint = true; + public var shouldSnapToOvershootSnapPoint = false; - var shouldDismissModalOnSnapToUnderShootSnapPoint = true; - var shouldDismissModalOnSnapToOverShootSnapPoint = false; + public var shouldDismissModalOnSnapToUnderShootSnapPoint = true; + public var shouldDismissModalOnSnapToOverShootSnapPoint = false; // MARK: - Properties - Layout-Related // ------------------------------------ - weak var modalViewController: UIViewController?; - weak var targetViewController: UIViewController?; + public weak var modalViewController: UIViewController?; + public weak var targetViewController: UIViewController?; - weak var targetView: UIView?; - weak var modalView: UIView?; + public weak var targetView: UIView?; + public weak var modalView: UIView?; - lazy var dummyModalView = UIView(); - lazy var modalWrapperView = UIView(); - lazy var modalWrapperTransformView = UIView(); - lazy var modalWrapperShadowView = UIView(); + public lazy var dummyModalView = UIView(); + public lazy var modalWrapperView = UIView(); + public lazy var modalWrapperTransformView = UIView(); + public lazy var modalWrapperShadowView = UIView(); - var prevModalFrame: CGRect = .zero; + public private(set) var prevModalFrame: CGRect = .zero; - var modalBackgroundView: UIView?; - var modalBackgroundVisualEffectView: UIVisualEffectView?; + public private(set) var modalBackgroundView: UIView?; + public private(set) var modalBackgroundVisualEffectView: UIVisualEffectView?; - var backgroundDimmingView: UIView?; - var backgroundVisualEffectView: UIVisualEffectView?; + public private(set) var backgroundDimmingView: UIView?; + public private(set) var backgroundVisualEffectView: UIVisualEffectView?; - private var modalFrame: CGRect! { + public private(set) var modalFrame: CGRect! { set { guard let newValue = newValue else { return }; self.prevModalFrame = dummyModalView.frame; @@ -140,7 +140,7 @@ class AdaptiveModalManager: NSObject { // MARK: - Properties - Interpolation Points // ------------------------------------------ - var prevInterpolationIndex: Int { + public private(set) var prevInterpolationIndex: Int { get { self.shouldSnapToOvershootSnapPoint ? self.prevOverrideInterpolationIndex @@ -156,7 +156,7 @@ class AdaptiveModalManager: NSObject { } }; - var nextInterpolationIndex: Int? { + public private(set) var nextInterpolationIndex: Int? { get { self.shouldSnapToOvershootSnapPoint ? self.nextOverrideInterpolationIndex @@ -172,7 +172,7 @@ class AdaptiveModalManager: NSObject { } }; - var currentInterpolationIndex: Int { + public private(set) var currentInterpolationIndex: Int { get { self.shouldSnapToOvershootSnapPoint ? self.currentOverrideInterpolationIndex @@ -188,7 +188,7 @@ class AdaptiveModalManager: NSObject { } }; - var interpolationSteps: [AdaptiveModalInterpolationPoint]! { + public private(set) var interpolationSteps: [AdaptiveModalInterpolationPoint]! { get { self.shouldUseOverrideSnapPoints ? self.overrideInterpolationPoints @@ -204,20 +204,20 @@ class AdaptiveModalManager: NSObject { } }; - var currentInterpolationStep: AdaptiveModalInterpolationPoint { + public var currentInterpolationStep: AdaptiveModalInterpolationPoint { self.interpolationSteps[self.currentInterpolationIndex]; }; - private var interpolationRangeInput: [CGFloat]! { + public var interpolationRangeInput: [CGFloat]! { self.interpolationSteps.map { $0.percent }; }; - private var interpolationRangeMaxInput: CGFloat? { + public var interpolationRangeMaxInput: CGFloat? { guard let targetView = self.targetView else { return nil }; return targetView.frame[keyPath: self.modalConfig.maxInputRangeKeyForRect]; }; - var currentSnapPointConfig: AdaptiveModalSnapPointConfig { + public var currentSnapPointConfig: AdaptiveModalSnapPointConfig { self.modalConfig.snapPoints[ self.currentInterpolationStep.snapPointIndex ]; @@ -364,22 +364,22 @@ class AdaptiveModalManager: NSObject { // MARK: - Computed Properties // --------------------------- - var isSwiping: Bool { + public var isSwiping: Bool { self.gestureInitialPoint != nil }; - var isAnimating: Bool { + public var isAnimating: Bool { self.modalAnimator != nil || (self.modalAnimator?.isRunning ?? false); }; - var currentSnapPointIndex: Int { + public var currentSnapPointIndex: Int { self.currentInterpolationStep.snapPointIndex }; // MARK: - Init // ------------ - init(modalConfig: AdaptiveModalConfig) { + public init(modalConfig: AdaptiveModalConfig) { self.modalConfig = modalConfig; super.init(); @@ -1341,7 +1341,6 @@ class AdaptiveModalManager: NSObject { private func getClosestSnapPoint(forCoord coord: CGFloat? = nil) -> ( interpolationIndex: Int, - snapPointConfig: AdaptiveModalSnapPointConfig, interpolationPoint: AdaptiveModalInterpolationPoint, snapDistance: CGFloat ) { @@ -1369,7 +1368,6 @@ class AdaptiveModalManager: NSObject { return ( interpolationIndex: closestInterpolationIndex, - snapPointConfig: self.modalConfig.snapPoints[snapPointIndex], interpolationPoint: interpolationPoint, snapDistance: closestSnapPoint.element ); @@ -1415,11 +1413,13 @@ class AdaptiveModalManager: NSObject { private func animateModal( to interpolationPoint: AdaptiveModalInterpolationPoint, + animator: UIViewPropertyAnimator? = nil, + extraAnimation: (() -> Void)? = nil, completion: ((UIViewAnimatingPosition) -> Void)? = nil ) { guard let modalView = self.modalView else { return }; - let animator: UIViewPropertyAnimator = { + let animator: UIViewPropertyAnimator = animator ?? { let gestureInitialVelocity = self.gestureInitialVelocity; let snapAnimationConfig = self.modalConfig.snapAnimationConfig; @@ -1438,19 +1438,19 @@ class AdaptiveModalManager: NSObject { self.modalAnimator = animator; animator.addAnimations { - interpolationPoint.apply(toModalView: modalView); - - interpolationPoint.apply(toModalWrapperView: self.modalWrapperView); - interpolationPoint.apply(toModalWrapperTransformView: self.modalWrapperTransformView); - interpolationPoint.apply(toModalWrapperShadowView: self.modalWrapperShadowView); - - interpolationPoint.apply(toDummyModalView: self.dummyModalView); - interpolationPoint.apply(toModalBackgroundView: self.modalBackgroundView); - interpolationPoint.apply(toBackgroundView: self.backgroundDimmingView); - - interpolationPoint.apply(toModalBackgroundEffectView: self.modalBackgroundVisualEffectView); - interpolationPoint.apply(toBackgroundVisualEffectView: self.backgroundVisualEffectView); - + extraAnimation?(); + + interpolationPoint.apply( + toModalView: modalView, + toModalWrapperView: self.modalWrapperView, + toModalWrapperTransformView: self.modalWrapperTransformView, + toModalWrapperShadowView: self.modalWrapperShadowView, + toDummyModalView: self.dummyModalView, + toModalBackgroundView: self.modalBackgroundView, + toBackgroundView: self.backgroundDimmingView, + toModalBackgroundEffectView: self.modalBackgroundVisualEffectView, + toBackgroundVisualEffectView: self.backgroundVisualEffectView + ); }; if let completion = completion { @@ -1507,11 +1507,17 @@ class AdaptiveModalManager: NSObject { }; }; - @objc func onKeyboardWillShow(notification: NSNotification) { + @objc private func onKeyboardWillShow(notification: NSNotification) { guard let keyboardValues = RNILayoutKeyboardValues(fromNotification: notification) else { return }; self.layoutKeyboardValues = keyboardValues; + self.computeSnapPoints(); + + self.animateModal( + to: self.currentInterpolationStep, + animator: keyboardValues.keyboardAnimator + ); print( "onKeyboardWillShow", @@ -1524,7 +1530,7 @@ class AdaptiveModalManager: NSObject { ); }; - @objc func onKeyboardDidShow(notification: NSNotification) { + @objc private func onKeyboardDidShow(notification: NSNotification) { guard let keyboardValues = RNILayoutKeyboardValues(fromNotification: notification) else { return }; @@ -1541,11 +1547,17 @@ class AdaptiveModalManager: NSObject { ); }; - @objc func onKeyboardWillHide(notification: NSNotification) { + @objc private func onKeyboardWillHide(notification: NSNotification) { guard let keyboardValues = RNILayoutKeyboardValues(fromNotification: notification) else { return }; - self.layoutKeyboardValues = keyboardValues; + self.clearLayoutKeyboardValues(); + self.computeSnapPoints(); + + self.animateModal( + to: self.currentInterpolationStep, + animator: keyboardValues.keyboardAnimator + ); print( "onKeyboardWillHide", @@ -1558,7 +1570,7 @@ class AdaptiveModalManager: NSObject { ); }; - @objc func onKeyboardDidHide(notification: NSNotification) { + @objc private func onKeyboardDidHide(notification: NSNotification) { guard let keyboardValues = RNILayoutKeyboardValues(fromNotification: notification) else { return }; @@ -1575,11 +1587,12 @@ class AdaptiveModalManager: NSObject { ); }; - @objc func onKeyboardWillChange(notification: NSNotification) { + @objc private func onKeyboardWillChange(notification: NSNotification) { guard let keyboardValues = RNILayoutKeyboardValues(fromNotification: notification) else { return }; self.layoutKeyboardValues = keyboardValues; + self.computeSnapPoints(); print( "onKeyboardWillChange", @@ -1592,11 +1605,12 @@ class AdaptiveModalManager: NSObject { ); }; - @objc func onKeyboardDidChange(notification: NSNotification) { + @objc private func onKeyboardDidChange(notification: NSNotification) { guard let keyboardValues = RNILayoutKeyboardValues(fromNotification: notification) else { return }; self.layoutKeyboardValues = keyboardValues; + self.computeSnapPoints(); print( "onKeyboardDidChange", @@ -1781,62 +1795,9 @@ class AdaptiveModalManager: NSObject { self.modalViewController?.dismiss(animated: false); }; - // MARK: - User-Invoked Functions - // ------------------------------ - - func prepareForPresentation( - modalView: UIView? = nil, - targetView: UIView? = nil, - shouldForceReset: Bool = false - ) { - guard let modalView = modalView ?? self.modalView, - let targetView = targetView ?? self.targetView - else { return }; - - let didViewsChange = - modalView !== self.modalView || targetView !== self.targetView; - - let shouldReset = - !self.didTriggerSetup || didViewsChange || shouldForceReset; - - if shouldReset { - self.cleanup(); - }; - - self.modalView = modalView; - self.targetView = targetView; - - self.computeSnapPoints(); - - if shouldReset { - self.setupInitViews(); - self.setupDummyModalView(); - self.setupGestureHandler(); - - self.setupAddViews(); - self.setupViewConstraints(); - self.setupObservers(); - }; - - self.updateModal(); - self.didTriggerSetup = true; - }; - - func prepareForPresentation( - viewControllerToPresent presentingVC: UIViewController, - presentingViewController presentedVC: UIViewController - ) { - self.modalViewController = presentingVC; - self.modalView = presentingVC.view; + // MARK: - Functions + // ----------------- - self.setupViewControllers(); - }; - - func notifyDidLayoutSubviews() { - self.computeSnapPoints(); - self.updateModal(); - }; - func snapTo( interpolationIndex nextIndex: Int, interpolationPoint: AdaptiveModalInterpolationPoint? = nil, @@ -1849,13 +1810,13 @@ class AdaptiveModalManager: NSObject { self.notifyOnModalWillSnap(); - self.animateModal(to: nextInterpolationPoint) { _ in + self.animateModal(to: nextInterpolationPoint, completion: { _ in self.currentInterpolationIndex = nextIndex; self.nextInterpolationIndex = nil; self.notifyOnModalDidSnap(); completion?(); - }; + }); }; func snapToClosestSnapPoint( @@ -1882,34 +1843,6 @@ class AdaptiveModalManager: NSObject { ); }; - func snapToClosestSnapPoint(completion: (() -> Void)? = nil) { - let closestSnapPoint = self.getClosestSnapPoint(forRect: self.modalFrame); - - let nextInterpolationIndex = - self.adjustInterpolationIndex(for: closestSnapPoint.interpolationIndex); - - let nextInterpolationPoint = - self.interpolationSteps[nextInterpolationIndex]; - - let prevFrame = self.modalFrame; - let nextFrame = nextInterpolationPoint.computedRect; - - guard nextInterpolationIndex != self.currentInterpolationIndex, - prevFrame != nextFrame - else { return }; - - self.snapTo(interpolationIndex: nextInterpolationIndex) { - completion?(); - }; - }; - - func snapToCurrentIndex(completion: (() -> Void)? = nil) { - self.snapTo( - interpolationIndex: self.currentInterpolationIndex, - completion: completion - ); - }; - func showModal(completion: (() -> Void)? = nil) { let nextIndex = self.modalConfig.initialSnapPointIndex; self.snapTo(interpolationIndex: nextIndex, completion: completion); @@ -1952,6 +1885,90 @@ class AdaptiveModalManager: NSObject { }; }; + // MARK: - User-Invoked Functions + // ------------------------------ + + public func prepareForPresentation( + modalView: UIView? = nil, + targetView: UIView? = nil, + shouldForceReset: Bool = false + ) { + guard let modalView = modalView ?? self.modalView, + let targetView = targetView ?? self.targetView + else { return }; + + let didViewsChange = + modalView !== self.modalView || targetView !== self.targetView; + + let shouldReset = + !self.didTriggerSetup || didViewsChange || shouldForceReset; + + if shouldReset { + self.cleanup(); + }; + + self.modalView = modalView; + self.targetView = targetView; + + self.computeSnapPoints(); + + if shouldReset { + self.setupInitViews(); + self.setupDummyModalView(); + self.setupGestureHandler(); + + self.setupAddViews(); + self.setupViewConstraints(); + self.setupObservers(); + }; + + self.updateModal(); + self.didTriggerSetup = true; + }; + + public func prepareForPresentation( + viewControllerToPresent presentingVC: UIViewController, + presentingViewController presentedVC: UIViewController + ) { + self.modalViewController = presentingVC; + self.modalView = presentingVC.view; + + self.setupViewControllers(); + }; + + public func notifyDidLayoutSubviews() { + self.computeSnapPoints(); + self.updateModal(); + }; + + public func snapToClosestSnapPoint(completion: (() -> Void)? = nil) { + let closestSnapPoint = self.getClosestSnapPoint(forRect: self.modalFrame); + + let nextInterpolationIndex = + self.adjustInterpolationIndex(for: closestSnapPoint.interpolationIndex); + + let nextInterpolationPoint = + self.interpolationSteps[nextInterpolationIndex]; + + let prevFrame = self.modalFrame; + let nextFrame = nextInterpolationPoint.computedRect; + + guard nextInterpolationIndex != self.currentInterpolationIndex, + prevFrame != nextFrame + else { return }; + + self.snapTo(interpolationIndex: nextInterpolationIndex) { + completion?(); + }; + }; + + public func snapToCurrentIndex(completion: (() -> Void)? = nil) { + self.snapTo( + interpolationIndex: self.currentInterpolationIndex, + completion: completion + ); + }; + public func presentModal( viewControllerToPresent modalVC: UIViewController, presentingViewController targetVC: UIViewController, @@ -2046,8 +2063,8 @@ class AdaptiveModalManager: NSObject { self.overrideSnapPoints = snapPoints; self.overrideInterpolationPoints = interpolationPoints; - self.animateModal(to: nextInterpolationPoint) { _ in + self.animateModal(to: nextInterpolationPoint, completion: { _ in completion?(); - }; + }); }; }; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalRangePropertyAnimator.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalRangePropertyAnimator.swift index da0c8daf..b7bca622 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalRangePropertyAnimator.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalRangePropertyAnimator.swift @@ -8,15 +8,18 @@ import UIKit -struct AdaptiveModalRangePropertyAnimator { +public struct AdaptiveModalRangePropertyAnimator { + + // MARK: - Properties + // ------------------ - var interpolationRangeStart: AdaptiveModalInterpolationPoint; - var interpolationRangeEnd: AdaptiveModalInterpolationPoint; + public var interpolationRangeStart: AdaptiveModalInterpolationPoint; + public var interpolationRangeEnd: AdaptiveModalInterpolationPoint; - let interpolationOutputKey: + public let interpolationOutputKey: KeyPath?; - var animator: UIViewPropertyAnimator; + public var animator: UIViewPropertyAnimator; private weak var component: AnyObject?; @@ -25,7 +28,10 @@ struct AdaptiveModalRangePropertyAnimator { self.interpolationRangeEnd ]}; - init( + // MARK: - Init + // ------------ + + public init( interpolationRangeStart: AdaptiveModalInterpolationPoint, interpolationRangeEnd: AdaptiveModalInterpolationPoint, forComponent component: T, @@ -54,7 +60,10 @@ struct AdaptiveModalRangePropertyAnimator { self.animator = animator; }; - func didRangeChange( + // MARK: - Functions + // ----------------- + + public func didRangeChange( interpolationRangeStart: AdaptiveModalInterpolationPoint, interpolationRangeEnd: AdaptiveModalInterpolationPoint ) -> Bool { @@ -65,7 +74,7 @@ struct AdaptiveModalRangePropertyAnimator { return didChange; }; - mutating func update( + public mutating func update( interpolationRangeStart: AdaptiveModalInterpolationPoint, interpolationRangeEnd: AdaptiveModalInterpolationPoint ){ @@ -79,11 +88,11 @@ struct AdaptiveModalRangePropertyAnimator { self.interpolationRangeEnd = interpolationRangeEnd; }; - func setFractionComplete(forPercent percent: CGFloat) { + public func setFractionComplete(forPercent percent: CGFloat) { self.animator.fractionComplete = percent; }; - func setFractionComplete( + public func setFractionComplete( forInputPercentValue inputPercentValue: CGFloat ) { let rangeOutput: [CGFloat] = { @@ -108,7 +117,7 @@ struct AdaptiveModalRangePropertyAnimator { self.setFractionComplete(forPercent: percent); }; - func clear(){ + public func clear(){ self.animator.stopAnimation(true); }; }; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapAnimationConfig.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapAnimationConfig.swift index 4bcd3f06..fcb983c6 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapAnimationConfig.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapAnimationConfig.swift @@ -7,14 +7,14 @@ import UIKit -struct AdaptiveModalSnapAnimationConfig { - static let `default`: Self = .init( +public struct AdaptiveModalSnapAnimationConfig { + public static let `default`: Self = .init( springDampingRatio: 0.9, springAnimationSettlingTime: 0.4, maxGestureVelocity: 15 ); - let springDampingRatio: CGFloat; - let springAnimationSettlingTime: CGFloat; - let maxGestureVelocity: CGFloat; + public let springDampingRatio: CGFloat; + public let springAnimationSettlingTime: CGFloat; + public let maxGestureVelocity: CGFloat; }; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPoint.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPoint.swift index 61434361..711dbb8a 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPoint.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPoint.swift @@ -7,12 +7,12 @@ import UIKit -struct AdaptiveModalSnapPointConfig { +public struct AdaptiveModalSnapPointConfig { // MARK: Types // ----------- - enum SnapPointKey: Equatable { + public enum SnapPointKey: Equatable { case undershootPoint, overshootPoint, unspecified; case string(_ stringKey: String); case index(_ indexKey: Int); @@ -21,15 +21,15 @@ struct AdaptiveModalSnapPointConfig { // MARK: Properties // ---------------- - let key: SnapPointKey; + public let key: SnapPointKey; - let snapPoint: RNILayout; - let animationKeyframe: AdaptiveModalAnimationConfig?; + public let snapPoint: RNILayout; + public let animationKeyframe: AdaptiveModalAnimationConfig?; // MARK: Init // ---------- - init( + public init( key: SnapPointKey = .unspecified, snapPoint: RNILayout, animationKeyframe: AdaptiveModalAnimationConfig? = nil @@ -39,7 +39,7 @@ struct AdaptiveModalSnapPointConfig { self.animationKeyframe = animationKeyframe; }; - init( + public init( key: SnapPointKey = .unspecified, fromSnapPointPreset snapPointPreset: AdaptiveModalSnapPointPreset, fromBaseLayoutConfig baseLayoutConfig: RNILayout @@ -55,7 +55,7 @@ struct AdaptiveModalSnapPointConfig { self.animationKeyframe = snapPointPreset.animationKeyframe; }; - init( + public init( fromBase base: Self, newKey: SnapPointKey, newSnapPoint: RNILayout? = nil, diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPointPreset.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPointPreset.swift index 55e773e8..a93b9d6c 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPointPreset.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalSnapPointPreset.swift @@ -7,10 +7,10 @@ import Foundation -struct AdaptiveModalSnapPointPreset { +public struct AdaptiveModalSnapPointPreset { - let layoutPreset: RNILayoutPreset; - let animationKeyframe: AdaptiveModalAnimationConfig?; + public let layoutPreset: RNILayoutPreset; + public let animationKeyframe: AdaptiveModalAnimationConfig?; init( layoutPreset: RNILayoutPreset, diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/CACornerMask+StaticAlias.swift b/experiments/swift-programmatic-modal/AdaptiveModal/CACornerMask+StaticAlias.swift new file mode 100644 index 00000000..628f90da --- /dev/null +++ b/experiments/swift-programmatic-modal/AdaptiveModal/CACornerMask+StaticAlias.swift @@ -0,0 +1,38 @@ +// +// StaticAlias.swift +// swift-programmatic-modal +// +// Created by Dominic Go on 6/21/23. +// + +import UIKit + +extension CACornerMask { + + public static let allCorners: Self = [ + .layerMinXMinYCorner, + .layerMaxXMinYCorner, + .layerMinXMaxYCorner, + .layerMaxXMaxYCorner, + ]; + + public static let topCorners: Self = [ + .layerMinXMinYCorner, + .layerMaxXMinYCorner, + ]; + + public static let bottomCorners: Self = [ + .layerMinXMaxYCorner, + .layerMaxXMaxYCorner, + ]; + + public static let leftCorners: Self = [ + .layerMinXMinYCorner, + .layerMinXMaxYCorner, + ]; + + public static let rightCorners: Self = [ + .layerMaxXMinYCorner, + .layerMaxXMaxYCorner, + ]; +}; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/UIBezierPath+VariadicCornerRadius.swift b/experiments/swift-programmatic-modal/AdaptiveModal/UIBezierPath+VariadicCornerRadius.swift index 5b3cfcc8..2db4b826 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/UIBezierPath+VariadicCornerRadius.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/UIBezierPath+VariadicCornerRadius.swift @@ -7,7 +7,7 @@ import UIKit -extension UIBezierPath { +public extension UIBezierPath { convenience init( shouldRoundRect rect: CGRect, diff --git a/experiments/swift-programmatic-modal/RNILayout/RNILayoutKeyboardValues.swift b/experiments/swift-programmatic-modal/RNILayout/RNILayoutKeyboardValues.swift index 2c91a86b..11d79c31 100644 --- a/experiments/swift-programmatic-modal/RNILayout/RNILayoutKeyboardValues.swift +++ b/experiments/swift-programmatic-modal/RNILayout/RNILayoutKeyboardValues.swift @@ -7,14 +7,34 @@ import UIKit -struct RNILayoutKeyboardValues { - var frameBegin: CGRect; - var frameEnd: CGRect; +public struct RNILayoutKeyboardValues { + + // MARK: - Properties + // ------------------ + + public var frameBegin: CGRect; + public var frameEnd: CGRect; + + public var animationDuration: CGFloat; + public var animationCurve: UIView.AnimationCurve; + + // MARK: - Computed Properties + // --------------------------- + + var keyboardAnimator: UIViewPropertyAnimator { + UIViewPropertyAnimator( + duration: self.animationDuration, + curve: self.animationCurve + ); + }; + + // MARK: - Functions + // ------------------ - var animationDuration: CGFloat; - var animationCurve: UIView.AnimationCurve; + public func computeKeyboardSize( + relativeToView targetView: UIView + ) -> CGSize? { - func computeKeyboardSize(relativeToView targetView: UIView) -> CGSize? { guard let window = targetView.window else { return nil }; // Get keyboard height. @@ -30,7 +50,11 @@ struct RNILayoutKeyboardValues { }; }; -extension RNILayoutKeyboardValues { +// MARK: - Init +// ------------ + +public extension RNILayoutKeyboardValues { + init?(fromNotification notification: NSNotification) { guard let userInfo = notification.userInfo else { return nil }; diff --git a/experiments/swift-programmatic-modal/RNILayout/RNILayoutPreset.swift b/experiments/swift-programmatic-modal/RNILayout/RNILayoutPreset.swift index 2d2e5245..cc66e46a 100644 --- a/experiments/swift-programmatic-modal/RNILayout/RNILayoutPreset.swift +++ b/experiments/swift-programmatic-modal/RNILayout/RNILayoutPreset.swift @@ -7,7 +7,7 @@ import UIKit -enum RNILayoutPreset { +public enum RNILayoutPreset { case offscreenBottom, offscreenTop, offscreenLeft, @@ -34,7 +34,7 @@ enum RNILayoutPreset { // MARK: Functions // --------------- - func getLayoutConfig( + public func getLayoutConfig( fromBaseLayoutConfig baseLayoutConfig: RNILayout ) -> RNILayout { diff --git a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValue+StaticInit.swift b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValue+StaticAlias.swift similarity index 68% rename from experiments/swift-programmatic-modal/RNILayout/RNILayoutValue+StaticInit.swift rename to experiments/swift-programmatic-modal/RNILayout/RNILayoutValue+StaticAlias.swift index 9ccfe61f..778065e5 100644 --- a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValue+StaticInit.swift +++ b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValue+StaticAlias.swift @@ -7,7 +7,7 @@ import UIKit -extension RNILayoutValue { +public extension RNILayoutValue { static let stretch: Self = .init(mode: .stretch); @@ -69,6 +69,40 @@ extension RNILayoutValue { ); }; + static func keyboardScreenRect( + rectKey: KeyPath, + offsetValue: RNILayoutValueMode? = nil, + offsetOperation: RNIComputableOffset.OffsetOperation? = nil, + minValue: RNILayoutValueMode? = nil, + maxValue: RNILayoutValueMode? = nil + ) -> Self { + + return .init( + mode: .keyboardScreenRect(rectKey: rectKey), + offsetValue: offsetValue, + offsetOperation: offsetOperation, + minValue: minValue, + maxValue: maxValue + ); + }; + + static func keyboardRelativeSize( + sizeKey: KeyPath, + offsetValue: RNILayoutValueMode? = nil, + offsetOperation: RNIComputableOffset.OffsetOperation? = nil, + minValue: RNILayoutValueMode? = nil, + maxValue: RNILayoutValueMode? = nil + ) -> Self { + + return .init( + mode: .keyboardRelativeSize(sizeKey: sizeKey), + offsetValue: offsetValue, + offsetOperation: offsetOperation, + minValue: minValue, + maxValue: maxValue + ); + }; + static func multipleValues( _ values: [RNILayoutValueMode], offsetValue: RNILayoutValueMode? = nil, diff --git a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValue.swift b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValue.swift index 3f920dbd..0a50bf33 100644 --- a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValue.swift +++ b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValue.swift @@ -30,7 +30,7 @@ public struct RNILayoutValue { // MARK: - Init // ------------ - init( + public init( mode: RNILayoutValueMode, offsetValue: RNILayoutValueMode? = nil, offsetOperation: RNIComputableOffset.OffsetOperation? = nil, diff --git a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueContext.swift b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueContext.swift index 1ebb972b..460b2905 100644 --- a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueContext.swift +++ b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueContext.swift @@ -9,7 +9,7 @@ import UIKit public struct RNILayoutValueContext { - static let `default`: Self = .init( + public static let `default`: Self = .init( targetRect: .zero, windowSize: nil, currentSize: nil, @@ -18,21 +18,21 @@ public struct RNILayoutValueContext { keyboardRelativeSize: nil ); - let targetRect: CGRect; + public let targetRect: CGRect; - let windowSize: CGSize?; - let currentSize: CGSize?; + public let windowSize: CGSize?; + public let currentSize: CGSize?; - let safeAreaInsets: UIEdgeInsets?; + public let safeAreaInsets: UIEdgeInsets?; - let keyboardScreenRect: CGRect?; - let keyboardRelativeSize: CGSize?; + public let keyboardScreenRect: CGRect?; + public let keyboardRelativeSize: CGSize?; - var targetSize: CGSize { + public var targetSize: CGSize { self.targetRect.size; }; - var screenSize: CGSize { + public var screenSize: CGSize { UIScreen.main.bounds.size; }; }; @@ -40,7 +40,7 @@ public struct RNILayoutValueContext { // MARK: - Init // ------------ -extension RNILayoutValueContext { +public extension RNILayoutValueContext { init( derivedFrom prev: Self, diff --git a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueMode.swift b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueMode.swift index 6dacc6ca..234d1cc9 100644 --- a/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueMode.swift +++ b/experiments/swift-programmatic-modal/RNILayout/RNILayoutValueMode.swift @@ -22,6 +22,14 @@ public enum RNILayoutValueMode { insetKey: KeyPath ); + case keyboardScreenRect( + rectKey: KeyPath + ); + + case keyboardRelativeSize( + sizeKey: KeyPath + ); + case multipleValues(_ values: [Self]); // MARK: Functions @@ -54,6 +62,12 @@ public enum RNILayoutValueMode { case let .safeAreaInsets(insetKey): return context.safeAreaInsets?[keyPath: insetKey]; + case let .keyboardScreenRect(rectKey): + return context.keyboardScreenRect?[keyPath: rectKey]; + + case let .keyboardRelativeSize(sizeKey): + return context.keyboardRelativeSize?[keyPath: sizeKey]; + case let .multipleValues(computableValues): return computableValues.reduce(0) { let computedValue = $1.compute( @@ -61,8 +75,7 @@ public enum RNILayoutValueMode { preferredSizeKey: preferredSizeKey ); - guard let computedValue = computedValue else { return 0 }; - return $0 + computedValue; + return $0 + (computedValue ?? 0); }; }; }; diff --git a/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift b/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift index 9294b484..a9048a4d 100644 --- a/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift +++ b/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift @@ -29,6 +29,7 @@ enum AdaptiveModalConfigTestPresets: CaseIterable { case demo07; case demo08; case demo09; + case demo10; var config: AdaptiveModalConfig { switch self { @@ -1003,17 +1004,27 @@ enum AdaptiveModalConfigTestPresets: CaseIterable { horizontalAlignment: .center, verticalAlignment: .bottom, width: .stretch, - height: .percent(percentValue: 0.3) + height: .percent(percentValue: 0.3), + marginLeft: .multipleValues([ + .safeAreaInsets(insetKey: \.left), + .constant(15), + ]), + marginRight: .multipleValues([ + .safeAreaInsets(insetKey: \.right), + .constant(15) + ]), + marginBottom: .multipleValues([ + .safeAreaInsets(insetKey: \.bottom), + .keyboardRelativeSize(sizeKey: \.height), + .constant(15) + ]) ), animationKeyframe: AdaptiveModalAnimationConfig( modalShadowOffset: .init(width: 0, height: -2), modalShadowOpacity: 0.2, modalShadowRadius: 7, - modalCornerRadius: 0, - modalMaskedCorners: [ - .layerMinXMinYCorner, - .layerMaxXMinYCorner - ], + modalCornerRadius: 12, + modalMaskedCorners: .allCorners, modalBackgroundOpacity: 0.9, modalBackgroundVisualEffect: UIBlurEffect(style: .systemUltraThinMaterial), modalBackgroundVisualEffectIntensity: 1, @@ -1027,6 +1038,70 @@ enum AdaptiveModalConfigTestPresets: CaseIterable { layoutPreset: .fitScreen ) ); + + case .demo10: return AdaptiveModalConfig( + snapPoints: [ + // snap point - 1 + AdaptiveModalSnapPointConfig( + snapPoint: .init( + horizontalAlignment: .left, + verticalAlignment: .center, + width: .percent(percentValue: 0.5), + height: .percent(percentValue: 0.5) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalScaleX: 1, + modalScaleY: 1, + modalShadowOffset: .init(width: 1, height: 1), + modalShadowOpacity: 0.3, + modalShadowRadius: 8, + modalCornerRadius: 10, + modalMaskedCorners: .rightCorners, + modalBackgroundOpacity: 0.87, + modalBackgroundVisualEffect: UIBlurEffect(style: .regular), + modalBackgroundVisualEffectIntensity: 1, + backgroundVisualEffect: UIBlurEffect(style: .regular), + backgroundVisualEffectIntensity: 0.04 + ) + ), + // snap point - 2 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .left, + verticalAlignment: .center, + width: RNILayoutValue( + mode: .stretch + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.85) + ), + marginRight: .constant(25) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalShadowOffset: .init(width: 2, height: 2), + modalShadowOpacity: 0.2, + modalShadowRadius: 15, + modalCornerRadius: 15, + modalBackgroundOpacity: 0.9, + modalBackgroundVisualEffectIntensity: 0.5, + backgroundVisualEffectIntensity: 0.5 + ) + ), + ], + snapDirection: .leftToRight, + undershootSnapPoint: .init( + layoutPreset: .offscreenLeft, + animationKeyframe: .init( + modalScaleX: 0.5, + modalScaleY: 0.5, + modalCornerRadius: 5, + backgroundVisualEffectIntensity: 0 + ) + ), + overshootSnapPoint: AdaptiveModalSnapPointPreset( + layoutPreset: .edgeRight + ) + ); }; }; }; diff --git a/experiments/swift-programmatic-modal/Test/AdaptiveModalPresentationTestViewController.swift b/experiments/swift-programmatic-modal/Test/AdaptiveModalPresentationTestViewController.swift index 09b57a31..c2c32c85 100644 --- a/experiments/swift-programmatic-modal/Test/AdaptiveModalPresentationTestViewController.swift +++ b/experiments/swift-programmatic-modal/Test/AdaptiveModalPresentationTestViewController.swift @@ -184,6 +184,7 @@ class AdaptiveModalPresentationTestViewController : UIViewController { .demo07, .demo08, .demo09, + .demo10, ]; var currentModalConfigPresetCounter = 0; @@ -321,7 +322,11 @@ class AdaptiveModalPresentationTestViewController : UIViewController { testVC.showCustomSnapPointButton = true; case .demo09: + //testVC.showCustomSnapPointButton = true; testVC.showTextInputField = true; + + case .demo10: + testVC.showCustomSnapPointButton = true; default: break; }; 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 227c7c66..ea3a6dea 100644 --- a/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj +++ b/experiments/swift-programmatic-modal/swift-programmatic-modal.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 880492582A23F89000D74E9F /* AdaptiveModalInterpolationPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 880492572A23F89000D74E9F /* AdaptiveModalInterpolationPoint.swift */; }; 88075E272A2121FE00B78388 /* AdaptiveModalUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88075E262A2121FE00B78388 /* AdaptiveModalUtilities.swift */; }; 88203DC12A122AC20088C8E2 /* RNIDynamicModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88203DC02A122AC20088C8E2 /* RNIDynamicModal.swift */; }; - 8849B6A72A3F7A7700A5F412 /* RNILayoutValue+StaticInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8849B6A62A3F7A7700A5F412 /* RNILayoutValue+StaticInit.swift */; }; + 8849B6A72A3F7A7700A5F412 /* RNILayoutValue+StaticAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8849B6A62A3F7A7700A5F412 /* RNILayoutValue+StaticAlias.swift */; }; 884A18F82A30516B0044AA66 /* AdaptiveModalPresentationTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A18F72A30516B0044AA66 /* AdaptiveModalPresentationTestViewController.swift */; }; 884A18FA2A3146CA0044AA66 /* AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A18F92A3146CA0044AA66 /* AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift */; }; 884A18FC2A3146FC0044AA66 /* AdaptiveModalManager+UIViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A18FB2A3146FC0044AA66 /* AdaptiveModalManager+UIViewControllerAnimatedTransitioning.swift */; }; @@ -20,6 +20,7 @@ 886AFCAF2A320DED004AC9FB /* RNILayoutValuePercentTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886AFCAE2A320DED004AC9FB /* RNILayoutValuePercentTarget.swift */; }; 886AFCB12A325B6F004AC9FB /* RNILayoutValueContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886AFCB02A325B6F004AC9FB /* RNILayoutValueContext.swift */; }; 887AA0572A4253050089F517 /* RNILayoutKeyboardValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887AA0562A4253050089F517 /* RNILayoutKeyboardValues.swift */; }; + 887C3BE22A42F9E00026B57C /* CACornerMask+StaticAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887C3BE12A42F9E00026B57C /* CACornerMask+StaticAlias.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 */; }; @@ -108,7 +109,7 @@ 880492572A23F89000D74E9F /* AdaptiveModalInterpolationPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalInterpolationPoint.swift; sourceTree = ""; }; 88075E262A2121FE00B78388 /* AdaptiveModalUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalUtilities.swift; sourceTree = ""; }; 88203DC02A122AC20088C8E2 /* RNIDynamicModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNIDynamicModal.swift; sourceTree = ""; }; - 8849B6A62A3F7A7700A5F412 /* RNILayoutValue+StaticInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RNILayoutValue+StaticInit.swift"; sourceTree = ""; }; + 8849B6A62A3F7A7700A5F412 /* RNILayoutValue+StaticAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RNILayoutValue+StaticAlias.swift"; sourceTree = ""; }; 884A18F72A30516B0044AA66 /* AdaptiveModalPresentationTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveModalPresentationTestViewController.swift; sourceTree = ""; }; 884A18F92A3146CA0044AA66 /* AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdaptiveModalManager+UIViewControllerTransitioningDelegate.swift"; sourceTree = ""; }; 884A18FB2A3146FC0044AA66 /* AdaptiveModalManager+UIViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdaptiveModalManager+UIViewControllerAnimatedTransitioning.swift"; sourceTree = ""; }; @@ -118,6 +119,7 @@ 886AFCAE2A320DED004AC9FB /* RNILayoutValuePercentTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNILayoutValuePercentTarget.swift; sourceTree = ""; }; 886AFCB02A325B6F004AC9FB /* RNILayoutValueContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNILayoutValueContext.swift; sourceTree = ""; }; 887AA0562A4253050089F517 /* RNILayoutKeyboardValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNILayoutKeyboardValues.swift; sourceTree = ""; }; + 887C3BE12A42F9E00026B57C /* CACornerMask+StaticAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CACornerMask+StaticAlias.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 = ""; }; @@ -436,7 +438,7 @@ 886AFCB02A325B6F004AC9FB /* RNILayoutValueContext.swift */, 886AFCAC2A3209D5004AC9FB /* RNILayoutValueMode.swift */, 886AFCAE2A320DED004AC9FB /* RNILayoutValuePercentTarget.swift */, - 8849B6A62A3F7A7700A5F412 /* RNILayoutValue+StaticInit.swift */, + 8849B6A62A3F7A7700A5F412 /* RNILayoutValue+StaticAlias.swift */, ); path = RNILayout; sourceTree = ""; @@ -544,6 +546,7 @@ isa = PBXGroup; children = ( 88E8C01D2A234B0A008C2FF8 /* UIBezierPath+VariadicCornerRadius.swift */, + 887C3BE12A42F9E00026B57C /* CACornerMask+StaticAlias.swift */, 88D018812A1D09DD004664D2 /* AdaptiveModalAnimationConfig.swift */, 88D018852A1D0A1D004664D2 /* AdaptiveModalSnapPoint.swift */, 88C2F45B2A275B2800DA7450 /* AdaptiveModalSnapPointPreset.swift */, @@ -643,6 +646,7 @@ 88D018232A1B3030004664D2 /* RNINavigationEventsReportingViewController.swift in Sources */, 88D018822A1D09DD004664D2 /* AdaptiveModalAnimationConfig.swift in Sources */, 88D018342A1B3030004664D2 /* UIModalPresentationStyle+Init.swift in Sources */, + 887C3BE22A42F9E00026B57C /* CACornerMask+StaticAlias.swift in Sources */, 884A18F82A30516B0044AA66 /* AdaptiveModalPresentationTestViewController.swift in Sources */, 88D0186B2A1B3030004664D2 /* RNIModalPresentationNotifiable.swift in Sources */, 886AFCAB2A31FF40004AC9FB /* RNILayoutValue.swift in Sources */, @@ -695,7 +699,7 @@ 88D0187E2A1B6CB3004664D2 /* BlurEffectTestViewController.swift in Sources */, 88D018272A1B3030004664D2 /* RNIInternalCleanupMode.swift in Sources */, 88D018782A1B3030004664D2 /* RNIComputableSize.swift in Sources */, - 8849B6A72A3F7A7700A5F412 /* RNILayoutValue+StaticInit.swift in Sources */, + 8849B6A72A3F7A7700A5F412 /* RNILayoutValue+StaticAlias.swift in Sources */, 88D018732A1B3030004664D2 /* RNIModalCustomSheetDetent.swift in Sources */, 88075E272A2121FE00B78388 /* AdaptiveModalUtilities.swift in Sources */, 88D018252A1B3030004664D2 /* UIViewController+Helpers.swift in Sources */,