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`.
  • Loading branch information
dominicstop committed May 31, 2023
1 parent 7eac231 commit 39e9b40
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

import UIKit

struct AdaptiveModalInterpolationPoint {
struct AdaptiveModalInterpolationPoint: Equatable {

let modalConfigIndex: Int;

/// The computed frames of the modal based on the snap points
let computedRect: CGRect;
Expand All @@ -16,11 +18,14 @@ struct AdaptiveModalInterpolationPoint {
let modalMaskedCorners: CACornerMask;

init(
modalConfigIndex: Int,
withTargetRect targetRect: CGRect,
currentSize: CGSize,
snapPointConfig: AdaptiveModalSnapPointConfig,
prevSnapPointConfig: AdaptiveModalSnapPointConfig? = nil
) {
self.modalConfigIndex = modalConfigIndex;

self.computedRect = snapPointConfig.snapPoint.computeRect(
withTargetRect: targetRect,
currentSize: currentSize
Expand Down Expand Up @@ -69,6 +74,7 @@ extension AdaptiveModalInterpolationPoint {

items.append(
AdaptiveModalInterpolationPoint(
modalConfigIndex: index,
withTargetRect: targetRect,
currentSize: currentSize,
snapPointConfig: snapConfig,
Expand All @@ -88,6 +94,7 @@ extension AdaptiveModalInterpolationPoint {
);

return AdaptiveModalInterpolationPoint(
modalConfigIndex: modalConfig.snapPointLastIndex + 1,
withTargetRect: targetRect,
currentSize: currentSize,
snapPointConfig: snapPointConfig,
Expand All @@ -98,3 +105,4 @@ extension AdaptiveModalInterpolationPoint {
return items;
};
};

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,75 @@ import UIKit

class AdaptiveModalManager {

struct ViewMaskedCornersAnimator {
var inputAxisKey: KeyPath<CGPoint, CGFloat>;

var animator: UIViewPropertyAnimator;

var interpolationRangeStart: AdaptiveModalInterpolationPoint;
var interpolationRangeEnd: AdaptiveModalInterpolationPoint;

private var inputRangeStart: CGFloat {
self.interpolationRangeStart
.computedRect.origin[keyPath: self.inputAxisKey];
};

private var inputRangeEnd: CGFloat {
self.interpolationRangeEnd
.computedRect.origin[keyPath: self.inputAxisKey];
};

init(
interpolationRangeStart: AdaptiveModalInterpolationPoint,
interpolationRangeEnd: AdaptiveModalInterpolationPoint,
forView view: UIView,
inputAxisKey: KeyPath<CGPoint, CGFloat>
) {
self.interpolationRangeStart = interpolationRangeStart;
self.interpolationRangeEnd = interpolationRangeEnd;

self.inputAxisKey = inputAxisKey;

let animator = UIViewPropertyAnimator(
duration: 0,
curve: .linear
);

animator.addAnimations {
view.layer.maskedCorners = interpolationRangeEnd.modalMaskedCorners
};

animator.stopAnimation(true);
self.animator = animator;
};

mutating func update(
interpolationRangeStart: AdaptiveModalInterpolationPoint,
interpolationRangeEnd: AdaptiveModalInterpolationPoint
){
let didChange =
interpolationRangeStart != self.interpolationRangeStart ||
interpolationRangeEnd != self.interpolationRangeEnd;

guard didChange else { return };

self.interpolationRangeStart = interpolationRangeStart;
self.interpolationRangeEnd = interpolationRangeEnd;
};

func setFractionComplete(forPercent percent: CGFloat){
self.animator.fractionComplete = percent;
};

func setFractionComplete(forInputValue inputValue: CGFloat) {
let inputRangeEndAdj = self.inputRangeEnd - self.inputRangeStart;
let inputValueAdj = inputValue - self.inputRangeStart;

let percent = inputValueAdj / inputValueAdj;
self.setFractionComplete(forPercent: percent);
};
};

// MARK: - Properties - Config-Related
// ------------------------------------

Expand All @@ -33,6 +102,8 @@ class AdaptiveModalManager {

var prevModalFrame: CGRect = .zero;

var modalViewMaskedCornersAnimator: ViewMaskedCornersAnimator?;

// MARK: - Properties
// -------------------

Expand Down Expand Up @@ -253,10 +324,41 @@ class AdaptiveModalManager {
$0.modalCornerRadius
}
);

guard let modalCornerRadius = modalCornerRadius else { return nil };
return modalCornerRadius;
};

func applyInterpolationToModalMaskedCorners(
forInputValue inputValue: CGFloat
) {
guard let modalView = self.modalView,
let inputRange = self.getInterpolationStepRange(
forInputValue: inputValue
)
else { return };

let animator: ViewMaskedCornersAnimator = {
if var animator = self.modalViewMaskedCornersAnimator {
animator.update(
interpolationRangeStart: inputRange.rangeStart,
interpolationRangeEnd: inputRange.rangeEnd
);

return animator;
};

return ViewMaskedCornersAnimator(
interpolationRangeStart: inputRange.rangeStart,
interpolationRangeEnd: inputRange.rangeEnd,
forView: modalView,
inputAxisKey: self.inputAxisKey
);
}();

animator.setFractionComplete(forInputValue: inputValue);
};

func applyInterpolationToModal(forPoint point: CGPoint){
guard let modalView = self.modalView else { return };
let inputValue = point[keyPath: self.inputAxisKey];
Expand All @@ -277,6 +379,8 @@ class AdaptiveModalManager {
if let nextModalRadius = nextModalRadius {
modalView.layer.cornerRadius = nextModalRadius;
};

self.applyInterpolationToModalMaskedCorners(forInputValue: inputValue);
};

func applyInterpolationToModal(forGesturePoint gesturePoint: CGPoint){
Expand Down Expand Up @@ -315,6 +419,10 @@ class AdaptiveModalManager {
self.gesturePoint = nil;
};

func clearAnimators(){
self.modalViewMaskedCornersAnimator = nil;
};

func animateModal(
to interpolationPoint: AdaptiveModalInterpolationPoint,
duration: CGFloat? = nil,
Expand Down Expand Up @@ -443,6 +551,56 @@ class AdaptiveModalManager {
);
};

func getInterpolationStepRange(
forInputValue inputValue: CGFloat
) -> (
rangeStart: AdaptiveModalInterpolationPoint,
rangeEnd: AdaptiveModalInterpolationPoint
)? {
guard let interpolationSteps = self.interpolationSteps,
let minStep = interpolationSteps.first,
let maxStep = interpolationSteps.last
else { return nil };

let lastIndex = interpolationSteps.count - 1;

if inputValue <= minStep.computedRect.origin[keyPath: self.inputAxisKey]{
return (
rangeStart: minStep,
rangeEnd: interpolationSteps[1]
);
};

if inputValue >= maxStep.computedRect.origin[keyPath: self.inputAxisKey]{
return (
rangeStart: interpolationSteps[lastIndex - 1],
rangeEnd: maxStep
);
};

let firstMatch = interpolationSteps.enumerated().first {
guard let nextItem = interpolationSteps[safeIndex: $0.offset + 1]
else { return false };

let coordCurrent =
$0.element.computedRect.origin[keyPath: self.inputAxisKey];

let coordNext =
nextItem.computedRect.origin[keyPath: self.inputAxisKey];

return coordCurrent >= inputValue && inputValue <= coordNext;
};

guard let rangeStart = firstMatch?.element,
let rangeStartIndex = firstMatch?.offset,
let rangeEnd = interpolationSteps[safeIndex: rangeStartIndex + 1]
else { return nil };

return (rangeStart, rangeEnd);
};

// MARK: - Functions - DisplayLink-Related
// ---------------------------------------

func startDisplayLink() {
let displayLink = CADisplayLink(
Expand Down Expand Up @@ -512,6 +670,8 @@ class AdaptiveModalManager {
self.applyInterpolationToModal(forGesturePoint: gesturePoint);

case .cancelled, .ended:
self.clearAnimators();

guard self.enableSnapping else {
self.clearGestureValues();
return;
Expand Down

0 comments on commit 39e9b40

Please sign in to comment.