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 26, 2023
1 parent e9e90f5 commit 9932f39
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ struct AdaptiveModalConfig {
enum Direction {
case horizontal;
case vertical;
case floating;
};

let snapPoints: [AdaptiveModalSnapPointConfig];
let snapDirection: Direction;

let entranceConfig: AdaptiveModalEntranceConfig;
// let entranceConfig: AdaptiveModalEntranceConfig;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// AdaptiveModalManager+Helpers.swift
// swift-programmatic-modal
//
// Created by Dominic Go on 5/27/23.
//

import Foundation

extension AdaptiveModalManager {

static func interpolate(
inputValue : CGFloat,
rangeInput : [CGFloat],
rangeOutput: [CGFloat]
) -> CGFloat? {

guard rangeInput.count == rangeOutput.count,
rangeInput.count >= 2
else { return nil };

// A - Extrapolate Left
if inputValue < rangeInput.first! {

let rangeInputStart = rangeInput.first!;
let rangeOutputStart = rangeOutput.first!;

let delta1 = rangeInputStart - inputValue;
let percent = delta1 / rangeInputStart;

// extrapolated "range output end"
let rangeOutputEnd = rangeOutputStart - (rangeOutput[1] - rangeOutputStart);

let interpolatedValue = RNIAnimator.EasingFunctions.lerp(
valueStart: rangeOutputEnd,
valueEnd: rangeOutputStart,
percent: percent
);

let delta2 = interpolatedValue - rangeOutputEnd;
return rangeOutputStart - delta2;
};

let (rangeStartIndex, rangeEndIndex): (Int, Int) = {
let rangeInputEnumerated = rangeInput.enumerated();

let match = rangeInputEnumerated.first {
guard let nextValue = rangeInput[safeIndex: $0.offset + 1]
else { return false };

return inputValue >= $0.element && inputValue < nextValue;
};

// B - Interpolate Between
if let match = match {
let rangeStartIndex = match.offset;
return (rangeStartIndex, rangeStartIndex + 1);
};

let lastIndex = rangeInput.count - 1;
let secondToLastIndex = rangeInput.count - 2;

// C - Extrapolate Right
return (secondToLastIndex, lastIndex);
}();

guard let rangeInputStart = rangeInput [safeIndex: rangeStartIndex],
let rangeInputEnd = rangeInput [safeIndex: rangeEndIndex ],
let rangeOutputStart = rangeOutput[safeIndex: rangeStartIndex],
let rangeOutputEnd = rangeOutput[safeIndex: rangeEndIndex ]
else { return nil };

let inputValueAdj = inputValue - rangeInputStart;
let rangeInputEndAdj = rangeInputEnd - rangeInputStart;

let progress = inputValueAdj / rangeInputEndAdj;

return RNIAnimator.EasingFunctions.lerp(
valueStart: rangeOutputStart,
valueEnd : rangeOutputEnd,
percent : progress
);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,149 +7,102 @@

import UIKit


enum AdaptiveModalConfigTestPresets {
case test01;

var config: AdaptiveModalConfig {
switch self {
case .test01: return AdaptiveModalConfig(
snapPoints: [
AdaptiveModalSnapPointConfig(
snapPoint: RNILayout(
horizontalAlignment: .center,
verticalAlignment: .bottom,
width: RNIComputableValue(
mode: .stretch
),
height: RNIComputableValue(
mode: .percent(percentValue: 0.1)
)
)
),
AdaptiveModalSnapPointConfig(
snapPoint: RNILayout(
horizontalAlignment: .center,
verticalAlignment: .bottom,
width: RNIComputableValue(
mode: .stretch
),
height: RNIComputableValue(
mode: .percent(percentValue: 0.3)
)
)
),
AdaptiveModalSnapPointConfig(
snapPoint: RNILayout(
horizontalAlignment: .center,
verticalAlignment: .bottom,
width: RNIComputableValue(
mode: .stretch
),
height: RNIComputableValue(
mode: .percent(percentValue: 0.7)
)
)
),
],
snapDirection: .vertical
);
};
};
};

class AdaptiveModalManager {

static func interpolate(
inputValue : CGFloat,
rangeInput : [CGFloat],
rangeOutput: [CGFloat]
) -> CGFloat? {
guard rangeInput.count == rangeOutput.count,
rangeInput.count >= 2
else { return nil };

// A - Extrapolate Left
if inputValue < rangeInput.first! {

let rangeInputStart = rangeInput.first!;
let rangeOutputStart = rangeOutput.first!;

let delta1 = rangeInputStart - inputValue;
let percent = delta1 / rangeInputStart;

// extrapolated "range output end"
let rangeOutputEnd = rangeOutputStart - (rangeOutput[1] - rangeOutputStart);

let interpolatedValue = RNIAnimator.EasingFunctions.lerp(
valueStart: rangeOutputEnd,
valueEnd: rangeOutputStart,
percent: percent
);

let delta2 = interpolatedValue - rangeOutputEnd;
return rangeOutputStart - delta2;
};
// MARK: - Properties
// -------------------

let (rangeStartIndex, rangeEndIndex): (Int, Int) = {
let rangeInputEnumerated = rangeInput.enumerated();

let match = rangeInputEnumerated.first {
guard let nextValue = rangeInput[safeIndex: $0.offset + 1]
else { return false };

return inputValue >= $0.element && inputValue < nextValue;
};
let modalConfig: AdaptiveModalConfig =
AdaptiveModalConfigTestPresets.test01.config;

// B - Interpolate Between
if let match = match {
let rangeStartIndex = match.offset;
return (rangeStartIndex, rangeStartIndex + 1);
};

let lastIndex = rangeInput.count - 1;
let secondToLastIndex = rangeInput.count - 2;

// C - Extrapolate Right
return (secondToLastIndex, lastIndex);
}();

guard let rangeInputStart = rangeInput [safeIndex: rangeStartIndex],
let rangeInputEnd = rangeInput [safeIndex: rangeEndIndex ],
let rangeOutputStart = rangeOutput[safeIndex: rangeStartIndex],
let rangeOutputEnd = rangeOutput[safeIndex: rangeEndIndex ]
else { return nil };

let inputValueAdj = inputValue - rangeInputStart;
let rangeInputEndAdj = rangeInputEnd - rangeInputStart;

let progress = inputValueAdj / rangeInputEndAdj;

return RNIAnimator.EasingFunctions.lerp(
valueStart: rangeOutputStart,
valueEnd : rangeOutputEnd,
percent : progress
);
};
var currentSnapPointIndex = 1;

var targetRectProvider: () -> CGRect;
var currentSizeProvider: () -> CGSize;

var gestureOffset: CGFloat?;

weak var modalView: UIView?;

let snapPoints: [RNILayout] = [
RNILayout(
horizontalAlignment: .center,
verticalAlignment: .bottom,
width: RNIComputableValue(
mode: .stretch
),
height: RNIComputableValue(
mode: .percent(percentValue: 0.1)
)
),
RNILayout(
horizontalAlignment: .center,
verticalAlignment: .bottom,
width: RNIComputableValue(
mode: .stretch
),
height: RNIComputableValue(
mode: .percent(percentValue: 0.3)
)
),
RNILayout(
horizontalAlignment: .center,
verticalAlignment: .bottom,
width: RNIComputableValue(
mode: .stretch
),
height: RNIComputableValue(
mode: .percent(percentValue: 0.7)
)
),
];

var currentSnapPointIndex = 1;
// MARK: - Computed Properties
// ---------------------------

/// Defines which axis of the gesture point to use to drive the interpolation
/// of the modal snap points
///
var inputAxisKey: KeyPath<CGPoint, CGFloat> = \.y;

// MARK: - Computed Properties
// ---------------------------
var inputAxisKey: KeyPath<CGPoint, CGFloat> {
switch self.modalConfig.snapDirection {
case .vertical : return \.y;
case .horizontal: return \.x;
};
};

/// The computed frames of the modal based on the snap points
var computedSnapRects: [CGRect] {
let targetSize = self.targetRectProvider();
let currentSize = self.currentSizeProvider();

return self.snapPoints.map {
$0.computeRect(
return self.modalConfig.snapPoints.map {
$0.snapPoint.computeRect(
withTargetRect: targetSize,
currentSize: currentSize
);
};
};

var currentSnapPoint: RNILayout {
return self.snapPoints[self.currentSnapPointIndex];
var currentSnapPointConfig: AdaptiveModalSnapPointConfig {
self.modalConfig.snapPoints[self.currentSnapPointIndex];
};


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

Expand All @@ -170,7 +123,7 @@ class AdaptiveModalManager {
forRect currentRect: CGRect
) -> (
nextSnapPointIndex: Int,
nextSnapPoint: RNILayout,
nextSnapPoint: AdaptiveModalSnapPointConfig,
computedRect: CGRect
) {
return self.getClosestSnapPoint(
Expand All @@ -182,7 +135,7 @@ class AdaptiveModalManager {
forGestureCoord: CGFloat
) -> (
nextSnapPointIndex: Int,
nextSnapPoint: RNILayout,
nextSnapPoint: AdaptiveModalSnapPointConfig,
computedRect: CGRect
) {
let snapRects = self.computedSnapRects;
Expand All @@ -197,10 +150,12 @@ class AdaptiveModalManager {
};
};

let closestSnapPointIndex = closestSnapPoint!.offset;

return (
nextSnapPointIndex: closestSnapPoint!.offset,
nextSnapPoint: self.snapPoints[closestSnapPoint!.offset],
computedRect: snapRects[closestSnapPoint!.offset]
nextSnapPoint: self.modalConfig.snapPoints[closestSnapPointIndex],
computedRect: snapRects[closestSnapPointIndex]
);
};

Expand All @@ -221,7 +176,6 @@ class AdaptiveModalManager {
return gestureCoord - modalCoord;
}();


if self.gestureOffset == nil {
self.gestureOffset = gestureOffset;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,12 @@ import UIKit
struct AdaptiveModalSnapPointConfig {
let snapPoint: RNILayout;
let animationKeyframe: AdaptiveModalAnimationConfig?;

init(
snapPoint: RNILayout,
animationKeyframe: AdaptiveModalAnimationConfig? = nil
) {
self.snapPoint = snapPoint
self.animationKeyframe = animationKeyframe
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ class RNIDraggableTestViewController : UIViewController {
let floatingView = self.floatingView;
self.view.addSubview(floatingView);

let initialSnapPoint = self.modalManager.currentSnapPoint;
let initialSnapPointConfig = self.modalManager.currentSnapPointConfig;
let initialSnapPoint = initialSnapPointConfig.snapPoint;

let computedRect = initialSnapPoint.computeRect(
withTargetRect: self.view.frame,
Expand Down
Loading

0 comments on commit 9932f39

Please sign in to comment.