Skip to content

Commit

Permalink
💫 Update: Exp - AdaptiveModal
Browse files Browse the repository at this point in the history
Summary:
* Update experiment/test - `swift-programmatic-modal/AdaptiveModal`.
* Custom modal corner radius.
  • Loading branch information
dominicstop committed Jun 6, 2023
1 parent 3a56f33 commit c1e5758
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ import UIKit


struct AdaptiveModalAnimationConfig {

struct CornerRadius: Equatable {
static let `default`: Self = .init(uniformRadius: 0);

let topLeftRadius : CGFloat;
let topRightRadius : CGFloat;
let bottomLeftRadius : CGFloat;
let bottomRightRadius: CGFloat;

init(
topLeftRadius : CGFloat,
topRightRadius : CGFloat,
bottomLeftRadius : CGFloat,
bottomRightRadius: CGFloat
) {
self.topLeftRadius = topLeftRadius;
self.topRightRadius = topRightRadius;
self.bottomLeftRadius = bottomLeftRadius;
self.bottomRightRadius = bottomRightRadius;
};

init(uniformRadius: CGFloat) {
self.topLeftRadius = uniformRadius;
self.topRightRadius = uniformRadius;
self.bottomLeftRadius = uniformRadius;
self.bottomRightRadius = uniformRadius;
};
};

let modalRotation: CGFloat?;

let modalScaleX: CGFloat?;
Expand All @@ -21,8 +50,7 @@ struct AdaptiveModalAnimationConfig {
let modalBackgroundColor: UIColor?;
let modalBackgroundOpacity: CGFloat?;

let modalCornerRadius: CGFloat?;
let modalMaskedCorners: CACornerMask?;
let modalCornerRadius: CornerRadius?;

let modalBackgroundVisualEffect: UIVisualEffect?;
let modalBackgroundVisualEffectOpacity: CGFloat?;
Expand All @@ -44,8 +72,7 @@ struct AdaptiveModalAnimationConfig {
modalOpacity: CGFloat? = nil,
modalBackgroundColor: UIColor? = nil,
modalBackgroundOpacity: CGFloat? = nil,
modalCornerRadius: CGFloat? = nil,
modalMaskedCorners: CACornerMask? = nil,
modalCornerRadius: CornerRadius? = nil,
modalBackgroundVisualEffect: UIVisualEffect? = nil,
modalBackgroundVisualEffectOpacity: CGFloat? = nil,
modalBackgroundVisualEffectIntensity: CGFloat? = nil,
Expand All @@ -68,7 +95,6 @@ struct AdaptiveModalAnimationConfig {
self.modalBackgroundOpacity = modalBackgroundOpacity;

self.modalCornerRadius = modalCornerRadius;
self.modalMaskedCorners = modalMaskedCorners;

self.modalBackgroundVisualEffect = modalBackgroundVisualEffect;
self.modalBackgroundVisualEffectOpacity = modalBackgroundVisualEffectOpacity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ struct AdaptiveModalInterpolationPoint: Equatable {
let modalBackgroundColor: UIColor;
let modalBackgroundOpacity: CGFloat;

let modalCornerRadius: CGFloat;
let modalMaskedCorners: CACornerMask;
let modalCornerRadius: AdaptiveModalAnimationConfig.CornerRadius;

let modalBackgroundVisualEffect: UIVisualEffect?;
let modalBackgroundVisualEffectOpacity: CGFloat;
Expand Down Expand Up @@ -81,6 +80,19 @@ struct AdaptiveModalInterpolationPoint: Equatable {
};
};

var modalCornerRadiusPath: UIBezierPath {
UIBezierPath(
shouldRoundRect: CGRect(
origin: .zero,
size: self.computedRect.size
),
topLeftRadius: self.modalCornerRadius.topLeftRadius,
topRightRadius: self.modalCornerRadius.topRightRadius,
bottomLeftRadius: self.modalCornerRadius.bottomLeftRadius,
bottomRightRadius: self.modalCornerRadius.bottomRightRadius
);
};

// MARK: - Init
// ------------

Expand Down Expand Up @@ -161,11 +173,7 @@ struct AdaptiveModalInterpolationPoint: Equatable {

self.modalCornerRadius = keyframeCurrent?.modalCornerRadius
?? keyframePrev?.modalCornerRadius
?? 0;

self.modalMaskedCorners = keyframeCurrent?.modalMaskedCorners
?? keyframePrev?.modalMaskedCorners
?? Self.DefaultMaskedCorners;
?? .default;

self.modalBackgroundVisualEffect = keyframeCurrent?.modalBackgroundVisualEffect
?? keyframePrev?.modalBackgroundVisualEffect;
Expand Down Expand Up @@ -245,9 +253,6 @@ struct AdaptiveModalInterpolationPoint: Equatable {
func apply(toModalView modalView: UIView){
modalView.transform = self.modalTransform;
modalView.alpha = self.modalOpacity;

modalView.layer.cornerRadius = self.modalCornerRadius;
modalView.layer.maskedCorners = self.modalMaskedCorners;
};

func apply(toModalWrapperView modalWrapperView: UIView){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ class AdaptiveModalManager {
weak var targetView: UIView?;
weak var modalView: UIView?;

var animator: UIViewPropertyAnimator?;
var modalAnimator: UIViewPropertyAnimator?;
var modalMaskAnimator: CABasicAnimation?;
var displayLink: CADisplayLink?;

weak var modalBackgroundView: UIView?;
Expand Down Expand Up @@ -87,11 +88,18 @@ class AdaptiveModalManager {
};

var isAnimating: Bool {
self.animator != nil || (self.animator?.isRunning ?? false);
self.modalAnimator != nil || (self.modalAnimator?.isRunning ?? false);
};

var currentPercent: CGFloat {
let currentCoord =
self.modalFrame!.origin[keyPath: self.modalConfig.inputValueKeyForPoint];

return currentCoord / self.interpolationRangeMaxInput!;
};

var displayLinkEndTimestamp: CFTimeInterval? {
guard let animator = self.animator,
guard let animator = self.modalAnimator,
let displayLinkStartTimestamp = self.displayLinkStartTimestamp
else { return nil };

Expand Down Expand Up @@ -565,11 +573,41 @@ class AdaptiveModalManager {

func interpolateModalBorderRadius(
forInputPercentValue inputPercentValue: CGFloat
) -> CGFloat? {

return self.interpolate(
) -> UIBezierPath? {
guard let modalView = self .modalView else { return nil };

let nextTopLeftRadius = self.interpolate(
inputValue: inputPercentValue,
rangeOutputKey: \.modalCornerRadius.topLeftRadius
);

let nextTopRightRadius = self.interpolate(
inputValue: inputPercentValue,
rangeOutputKey: \.modalCornerRadius.topRightRadius
);

let nextBottomLeftRadius = self.interpolate(
inputValue: inputPercentValue,
rangeOutputKey: \.modalCornerRadius
rangeOutputKey: \.modalCornerRadius.bottomLeftRadius
);

let nextBottomRightRadius = self.interpolate(
inputValue: inputPercentValue,
rangeOutputKey: \.modalCornerRadius.bottomRightRadius
);

guard let nextTopLeftRadius = nextTopLeftRadius,
let nextTopRightRadius = nextTopRightRadius,
let nextBottomLeftRadius = nextBottomLeftRadius,
let nextBottomRightRadius = nextBottomRightRadius
else { return nil };

return UIBezierPath(
shouldRoundRect: modalView.bounds,
topLeftRadius: nextTopLeftRadius,
topRightRadius: nextTopRightRadius,
bottomLeftRadius: nextBottomLeftRadius,
bottomRightRadius: nextBottomRightRadius
);
};

Expand Down Expand Up @@ -693,13 +731,18 @@ class AdaptiveModalManager {
)
);

AdaptiveModalUtilities.unwrapAndSetProperty(
forObject: modalView,
forPropertyKey: \.layer.cornerRadius,
withValue: self.interpolateModalBorderRadius(
modalView.layer.mask = {
let cornerRadiusPath = self.interpolateModalBorderRadius(
forInputPercentValue: inputPercentValue
)
);
);

guard let cornerRadiusPath = cornerRadiusPath else { return nil };

let shape = CAShapeLayer();
shape.path = cornerRadiusPath.cgPath;

return shape;
}();

AdaptiveModalUtilities.unwrapAndSetProperty(
forObject: self.modalBackgroundView,
Expand Down Expand Up @@ -836,7 +879,10 @@ class AdaptiveModalManager {
) {
guard let modalView = self.modalView else { return };

let animator: UIViewPropertyAnimator = {
// MARK: Make Animator - Modal Animator
// ------------------------------------

let modalAnimator: UIViewPropertyAnimator = {
// A - Animation based on duration
if let duration = duration {
return UIViewPropertyAnimator(
Expand Down Expand Up @@ -867,9 +913,9 @@ class AdaptiveModalManager {
);
}();

self.animator = animator;
self.modalAnimator = modalAnimator;

animator.addAnimations {
modalAnimator.addAnimations {
interpolationPoint.apply(toModalView: modalView);
interpolationPoint.apply(toModalWrapperView: self.modalWrapperView);

Expand All @@ -882,14 +928,56 @@ class AdaptiveModalManager {
};

if let completion = completion {
animator.addCompletion(completion);
modalAnimator.addCompletion(completion);
};

animator.addCompletion { _ in
self.animator = nil;
modalAnimator.addCompletion { _ in
self.modalAnimator = nil;
};

// MARK: Make Animator - Modal Layer Mask Animator
// -----------------------------------------------

let modalLayerMask = modalView.layer.mask as? CAShapeLayer;

let modalMaskAnimator: CABasicAnimation? = {
guard let modalLayerMask = modalLayerMask else { return nil };

let animator = CABasicAnimation(keyPath: "path");

animator.fromValue = modalLayerMask.path;
animator.toValue = interpolationPoint.modalCornerRadiusPath.cgPath;

animator.duration = modalAnimator.duration;

animator.timingFunction =
CAMediaTimingFunction(name: CAMediaTimingFunctionName.default);

animator.isRemovedOnCompletion = false;
animator.fillMode = .both;

modalLayerMask.add(animator, forKey: nil);

return animator;
}();

self.modalMaskAnimator = modalMaskAnimator;

let startModalMaskAnimator = {
guard let modalLayerMask = modalLayerMask,
modalMaskAnimator != nil
else { return };

CATransaction.begin();
modalView.layer.mask = modalLayerMask;
CATransaction.commit();
};

// MARK: Start Animators
// ---------------------

animator.startAnimation();
modalAnimator.startAnimation();
startModalMaskAnimator();
};

func getClosestSnapPoint(forCoord coord: CGFloat? = nil) -> (
Expand Down Expand Up @@ -1059,10 +1147,9 @@ class AdaptiveModalManager {
switch gesture.state {
case .began:
self.gestureInitialPoint = gesturePoint;
self.modalAnimator?.stopAnimation(true);

case .changed:
self.animator?.stopAnimation(true);

self.applyInterpolationToModal(forGesturePoint: gesturePoint);
self.onModalWillSnap();

Expand All @@ -1085,6 +1172,8 @@ class AdaptiveModalManager {
return;
};

self.modalAnimator?.stopAnimation(true);

self.animateModal(to: closestSnapPoint.interpolationPoint) { _ in
self.currentInterpolationIndex =
closestSnapPoint.interpolationIndex;
Expand Down
Loading

0 comments on commit c1e5758

Please sign in to comment.