diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalAnimationConfig.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalAnimationConfig.swift index 3f1c4483..5577a7b4 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalAnimationConfig.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalAnimationConfig.swift @@ -9,31 +9,39 @@ import UIKit struct AdaptiveModalAnimationConfig { - let modalRotation: CGFloat?; + var modalRotation: CGFloat?; - let modalScaleX: CGFloat?; - let modalScaleY: CGFloat?; + var modalScaleX: CGFloat?; + var modalScaleY: CGFloat?; - let modalTranslateX: CGFloat?; - let modalTranslateY: CGFloat?; + var modalTranslateX: CGFloat?; + var modalTranslateY: CGFloat?; - let modalOpacity: CGFloat?; - let modalBackgroundColor: UIColor?; - let modalBackgroundOpacity: CGFloat?; + var modalOpacity: CGFloat?; + var modalBackgroundColor: UIColor?; + var modalBackgroundOpacity: CGFloat?; - let modalCornerRadius: CGFloat?; - let modalMaskedCorners: CACornerMask?; + var modalBorderWidth: CGFloat?; + var modalBorderColor: UIColor?; - let modalBackgroundVisualEffect: UIVisualEffect?; - let modalBackgroundVisualEffectOpacity: CGFloat?; - let modalBackgroundVisualEffectIntensity: CGFloat?; + var modalShadowColor: UIColor?; + var modalShadowOffset: CGSize?; + var modalShadowOpacity: CGFloat?; + var modalShadowRadius: CGFloat?; - let backgroundColor: UIColor?; - let backgroundOpacity: CGFloat?; + var modalCornerRadius: CGFloat?; + var modalMaskedCorners: CACornerMask?; - let backgroundVisualEffect: UIVisualEffect?; - let backgroundVisualEffectOpacity: CGFloat?; - let backgroundVisualEffectIntensity: CGFloat?; + var modalBackgroundVisualEffect: UIVisualEffect?; + var modalBackgroundVisualEffectOpacity: CGFloat?; + var modalBackgroundVisualEffectIntensity: CGFloat?; + + var backgroundColor: UIColor?; + var backgroundOpacity: CGFloat?; + + var backgroundVisualEffect: UIVisualEffect?; + var backgroundVisualEffectOpacity: CGFloat?; + var backgroundVisualEffectIntensity: CGFloat?; init( modalRotation: CGFloat? = nil, @@ -44,6 +52,12 @@ struct AdaptiveModalAnimationConfig { modalOpacity: CGFloat? = nil, modalBackgroundColor: UIColor? = nil, modalBackgroundOpacity: CGFloat? = nil, + modalBorderWidth: CGFloat? = nil, + modalBorderColor: UIColor? = nil, + modalShadowColor: UIColor? = nil, + modalShadowOffset: CGSize? = nil, + modalShadowOpacity: CGFloat? = nil, + modalShadowRadius: CGFloat? = nil, modalCornerRadius: CGFloat? = nil, modalMaskedCorners: CACornerMask? = nil, modalBackgroundVisualEffect: UIVisualEffect? = nil, @@ -67,6 +81,14 @@ struct AdaptiveModalAnimationConfig { self.modalBackgroundColor = modalBackgroundColor; self.modalBackgroundOpacity = modalBackgroundOpacity; + self.modalBorderWidth = modalBorderWidth; + self.modalBorderColor = modalBorderColor; + + self.modalShadowColor = modalShadowColor; + self.modalShadowOffset = modalShadowOffset; + self.modalShadowOpacity = modalShadowOpacity; + self.modalShadowRadius = modalShadowRadius; + self.modalCornerRadius = modalCornerRadius; self.modalMaskedCorners = modalMaskedCorners; diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift index 2b7b435f..e0db4b77 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalInterpolationPoint.swift @@ -9,13 +9,13 @@ import UIKit struct AdaptiveModalInterpolationPoint: Equatable { - private static let DefaultMaskedCorners: CACornerMask = [ + static let DefaultMaskedCorners: CACornerMask = [ .layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner, ]; - + // MARK: - Properties // ------------------ @@ -40,6 +40,14 @@ struct AdaptiveModalInterpolationPoint: Equatable { var modalBackgroundColor: UIColor; var modalBackgroundOpacity: CGFloat; + var modalBorderWidth: CGFloat; + var modalBorderColor: UIColor; + + var modalShadowColor: UIColor; + var modalShadowOffset: CGSize; + var modalShadowOpacity: CGFloat; + var modalShadowRadius: CGFloat; + var modalCornerRadius: CGFloat; var modalMaskedCorners: CACornerMask; @@ -81,8 +89,105 @@ struct AdaptiveModalInterpolationPoint: Equatable { }; }; - // MARK: - Init - // ------------ + // MARK: - Functions + // ----------------- + + func getModalTransform( + shouldApplyRotation: Bool = true, + shouldApplyScale: Bool = true, + shouldApplyTranslate: Bool = true + ) -> CGAffineTransform { + + var transforms: [CGAffineTransform] = []; + + if shouldApplyRotation, + self.modalRotation != 0 { + + transforms.append( + .init(rotationAngle: self.modalRotation) + ); + }; + + if shouldApplyScale, + self.modalScaleX != 1 && self.modalScaleY != 1 { + + transforms.append( + .init(scaleX: self.modalScaleX, y: self.modalScaleY) + ); + }; + + if shouldApplyTranslate, + self.modalTranslateX != 0 && self.modalTranslateY != 0 { + + transforms.append( + .init(translationX: self.modalTranslateX, y: self.modalTranslateY) + ); + }; + + if transforms.isEmpty { + return .identity; + }; + + return transforms.reduce(.identity){ + $0.concatenating($1); + }; + }; + + func apply(toModalView modalView: UIView){ + 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; + + // 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){ + dummyModalView.frame = self.computedRect; + }; + + func apply(toModalBackgroundView modalBgView: UIView?){ + modalBgView?.alpha = self.modalBackgroundOpacity; + modalBgView?.backgroundColor = self.modalBackgroundColor; + }; + + func apply(toModalBackgroundEffectView effectView: UIVisualEffectView?){ + effectView?.alpha = self.modalBackgroundVisualEffectOpacity; + }; + + func apply(toBackgroundView bgView: UIView?){ + bgView?.alpha = self.backgroundOpacity; + }; + + func apply(toBackgroundVisualEffectView effectView: UIView?){ + effectView?.alpha = self.backgroundVisualEffectOpacity; + }; +}; + +// MARK: - Init +// ------------ + +extension AdaptiveModalInterpolationPoint { init( usingModalConfig modalConfig: AdaptiveModalConfig, @@ -157,6 +262,30 @@ struct AdaptiveModalInterpolationPoint: Equatable { self.modalBackgroundOpacity = keyframeCurrent?.modalBackgroundOpacity ?? keyframePrev?.modalBackgroundOpacity ?? 1; + + self.modalBorderWidth = keyframeCurrent?.modalBorderWidth + ?? keyframePrev?.modalBorderWidth + ?? 0; + + self.modalBorderColor = keyframeCurrent?.modalBorderColor + ?? keyframePrev?.modalBorderColor + ?? .black; + + self.modalShadowColor = keyframeCurrent?.modalShadowColor + ?? keyframePrev?.modalShadowColor + ?? .black; + + self.modalShadowOffset = keyframeCurrent?.modalShadowOffset + ?? keyframePrev?.modalShadowOffset + ?? .zero; + + self.modalShadowOpacity = keyframeCurrent?.modalShadowOpacity + ?? keyframePrev?.modalShadowOpacity + ?? 0; + + self.modalShadowRadius = keyframeCurrent?.modalShadowRadius + ?? keyframePrev?.modalShadowRadius + ?? 0; self.modalCornerRadius = keyframeCurrent?.modalCornerRadius ?? keyframePrev?.modalCornerRadius @@ -196,88 +325,11 @@ struct AdaptiveModalInterpolationPoint: Equatable { ?? keyframePrev?.backgroundVisualEffectIntensity ?? (isFirstSnapPoint ? 0 : 1); }; - - // MARK: - Functions - // ----------------- - - func getModalTransform( - shouldApplyRotation: Bool = true, - shouldApplyScale: Bool = true, - shouldApplyTranslate: Bool = true - ) -> CGAffineTransform { - - var transforms: [CGAffineTransform] = []; - - if shouldApplyRotation, - self.modalRotation != 0 { - - transforms.append( - .init(rotationAngle: self.modalRotation) - ); - }; - - if shouldApplyScale, - self.modalScaleX != 1 && self.modalScaleY != 1 { - - transforms.append( - .init(scaleX: self.modalScaleX, y: self.modalScaleY) - ); - }; - - if shouldApplyTranslate, - self.modalTranslateX != 0 && self.modalTranslateY != 0 { - - transforms.append( - .init(translationX: self.modalTranslateX, y: self.modalTranslateY) - ); - }; - - if transforms.isEmpty { - return .identity; - }; - - return transforms.reduce(.identity){ - $0.concatenating($1); - }; - }; - - func apply(toModalView modalView: UIView){ - 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(toDummyModalView dummyModalView: UIView){ - dummyModalView.frame = self.computedRect; - }; - - func apply(toModalBackgroundView modalBgView: UIView?){ - modalBgView?.alpha = self.modalBackgroundOpacity; - modalBgView?.backgroundColor = self.modalBackgroundColor; - }; - - func apply(toModalBackgroundEffectView effectView: UIVisualEffectView?){ - effectView?.alpha = self.modalBackgroundVisualEffectOpacity; - }; - - func apply(toBackgroundView bgView: UIView?){ - bgView?.alpha = self.backgroundOpacity; - }; - - func apply(toBackgroundVisualEffectView effectView: UIView?){ - effectView?.alpha = self.backgroundVisualEffectOpacity; - }; }; +// MARK: - Helpers +// --------------- + extension AdaptiveModalInterpolationPoint { static func compute( diff --git a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift index fa0186aa..c56267ec 100644 --- a/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift +++ b/experiments/swift-programmatic-modal/AdaptiveModal/AdaptiveModalManager.swift @@ -638,6 +638,27 @@ class AdaptiveModalManager: NSObject { return nextTransform; }; + + private func interpolateModalShadowOffset( + forInputPercentValue inputPercentValue: CGFloat + ) -> CGSize? { + + let nextWidth = self.interpolate( + inputValue: inputPercentValue, + rangeOutputKey: \.modalShadowOffset.width + ); + + let nextHeight = self.interpolate( + inputValue: inputPercentValue, + rangeOutputKey: \.modalShadowOffset.height + ); + + guard let nextWidth = nextWidth, + let nextHeight = nextHeight + else { return nil }; + + return CGSize(width: nextWidth, height: nextHeight); + }; private func interpolateModalBorderRadius( forInputPercentValue inputPercentValue: CGFloat @@ -769,6 +790,72 @@ class AdaptiveModalManager: NSObject { ) ); + AdaptiveModalUtilities.unwrapAndSetProperty( + forObject: self.modalWrapperShadowView, + forPropertyKey: \.layer.borderWidth, + withValue: self.interpolate( + inputValue: inputPercentValue, + rangeOutputKey: \.modalBorderWidth + ) + ); + + AdaptiveModalUtilities.unwrapAndSetProperty( + forObject: self.modalWrapperShadowView, + forPropertyKey: \.layer.borderColor, + withValue: { + let color = self.interpolateColor( + inputValue: inputPercentValue, + rangeOutputKey: \.modalBorderColor + ); + + return color?.cgColor; + }() + ); + + AdaptiveModalUtilities.unwrapAndSetProperty( + forObject: self.modalWrapperShadowView, + forPropertyKey: \.layer.shadowColor, + withValue: { + let color = self.interpolateColor( + inputValue: inputPercentValue, + rangeOutputKey: \.modalShadowColor + ); + + return color?.cgColor; + }() + ); + + AdaptiveModalUtilities.unwrapAndSetProperty( + forObject: self.modalWrapperShadowView, + forPropertyKey: \.layer.shadowOffset, + withValue: self.interpolateModalShadowOffset( + forInputPercentValue: inputPercentValue + ) + ); + + AdaptiveModalUtilities.unwrapAndSetProperty( + forObject: self.modalWrapperShadowView, + forPropertyKey: \.layer.shadowOpacity, + withValue: { + let value = self.interpolate( + inputValue: inputPercentValue, + rangeOutputKey: \.modalShadowOpacity + ); + + guard let value = value else { return nil }; + return Float(value); + }() + ); + + AdaptiveModalUtilities.unwrapAndSetProperty( + forObject: self.modalWrapperShadowView, + forPropertyKey: \.layer.shadowRadius, + withValue: self.interpolate( + inputValue: inputPercentValue, + rangeOutputKey: \.modalShadowRadius + ) + ); + AdaptiveModalUtilities.unwrapAndSetProperty( forObject: modalView, forPropertyKey: \.layer.cornerRadius, @@ -1097,6 +1184,7 @@ class AdaptiveModalManager: NSObject { 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); diff --git a/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift b/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift index 9d9e41c4..5404ade4 100644 --- a/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift +++ b/experiments/swift-programmatic-modal/Test/AdaptiveModalConfigTestPresets.swift @@ -9,9 +9,10 @@ import UIKit enum AdaptiveModalConfigTestPresets: CaseIterable { - static let `default`: Self = .testModalTransform01; + static let `default`: Self = .testModalBorderAndShadow01; case testModalTransform01; + case testModalBorderAndShadow01; case test01; case test02; @@ -88,6 +89,79 @@ enum AdaptiveModalConfigTestPresets: CaseIterable { layoutPreset: .fitScreenVertically ) ); + + case .testModalBorderAndShadow01: return AdaptiveModalConfig( + snapPoints: [ + // snap point - 0 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .percent(percentValue: 0.8) + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.2) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalBorderWidth: 2, + modalBorderColor: .blue, + modalShadowColor: .blue, + modalShadowOffset: .init(width: 3, height: 3), + modalShadowOpacity: 0.4, + modalShadowRadius: 4.0 + ) + ), + + // snap point - 1 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .percent(percentValue: 0.8) + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.4) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalBorderWidth: 4, + modalBorderColor: .cyan, + modalShadowColor: .green, + modalShadowOffset: .init(width: 6, height: 6), + modalShadowOpacity: 0.5, + modalShadowRadius: 5 + ) + ), + // snap point - 2 + AdaptiveModalSnapPointConfig( + snapPoint: RNILayout( + horizontalAlignment: .center, + verticalAlignment: .bottom, + width: RNILayoutValue( + mode: .percent(percentValue: 0.9) + ), + height: RNILayoutValue( + mode: .percent(percentValue: 0.7) + ) + ), + animationKeyframe: AdaptiveModalAnimationConfig( + modalBorderWidth: 8, + modalBorderColor: .green, + modalShadowColor: .purple, + modalShadowOffset: .init(width: 9, height: 9), + modalShadowOpacity: 0.9, + modalShadowRadius: 7 + ) + ), + ], + snapDirection: .bottomToTop, + overshootSnapPoint: AdaptiveModalSnapPointPreset( + layoutPreset: .fitScreenVertically + ) + ); case .test01: return AdaptiveModalConfig( snapPoints: [ diff --git a/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift b/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift index 70ffe693..5eca8488 100644 --- a/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift +++ b/experiments/swift-programmatic-modal/Test/RNIDraggableTestViewController.swift @@ -17,8 +17,6 @@ class RNIDraggableTestViewController : UIViewController { ); manager.eventDelegate = self; - //manager.shouldSnapToUnderShootSnapPoint = false; - return manager; }();