mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
chore: cleanup
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Observable, EventData, Page, ShowModalOptions, SharedTransition, ModalTransition, PageTransition } from '@nativescript/core';
|
||||
import { Observable, EventData, Page, ShowModalOptions, SharedTransition, ModalTransition, PageTransition, FadeTransition, SlideTransition } from '@nativescript/core';
|
||||
let page: Page;
|
||||
|
||||
export function navigatingTo(args: EventData) {
|
||||
|
||||
@@ -33,11 +33,9 @@ const majorVersion = iOSNativeHelper.MajorVersion;
|
||||
export class View extends ViewCommon implements ViewDefinition {
|
||||
nativeViewProtected: UIView;
|
||||
viewController: UIViewController;
|
||||
transitionInteractiveCtrl: UIPercentDrivenInteractiveTransition;
|
||||
private _popoverPresentationDelegate: IOSHelper.UIPopoverPresentationControllerDelegateImp;
|
||||
private _adaptivePresentationDelegate: IOSHelper.UIAdaptivePresentationControllerDelegateImp;
|
||||
private _transitioningDelegate: UIViewControllerTransitioningDelegateImpl;
|
||||
private _interactiveDismissGesture: (args: PanGestureEventData) => void;
|
||||
|
||||
/**
|
||||
* Track modal open animated options to use same option upon close
|
||||
@@ -471,18 +469,15 @@ export class View extends ViewCommon implements ViewDefinition {
|
||||
if (options.transition) {
|
||||
controller.modalPresentationStyle = UIModalPresentationStyle.Custom;
|
||||
if (options.transition.instance) {
|
||||
this._transitioningDelegate = UIViewControllerTransitioningDelegateImpl.initWithOwner(new WeakRef(options.transition.instance), new WeakRef(this));
|
||||
this._transitioningDelegate = UIViewControllerTransitioningDelegateImpl.initWithOwner(new WeakRef(options.transition.instance));
|
||||
controller.transitioningDelegate = this._transitioningDelegate;
|
||||
this.transitionId = options.transition.instance.id;
|
||||
const transitionState = SharedTransition.getState(options.transition.instance.id);
|
||||
if (transitionState?.interactive?.dismiss) {
|
||||
this._updateInteractiveTransition({
|
||||
options: transitionState?.interactive?.dismiss,
|
||||
});
|
||||
// interactive transitions via gestures
|
||||
// TODO - these could be typed as: boolean | (view: View) => void
|
||||
// to allow users to define their own custom gesture dismissals
|
||||
this._interactiveDismissGesture = this._interactiveDismissGestureHandler.bind(this);
|
||||
this.on('pan', this._interactiveDismissGesture);
|
||||
options.transition.instance.setupInteractiveGesture(this._closeModalCallback.bind(this), this);
|
||||
}
|
||||
}
|
||||
} else if (options.fullscreen) {
|
||||
@@ -565,47 +560,6 @@ export class View extends ViewCommon implements ViewDefinition {
|
||||
controller = null;
|
||||
}
|
||||
|
||||
private _interactiveDismissGestureHandler(args: PanGestureEventData) {
|
||||
if (args?.ios?.view) {
|
||||
this._updateInteractiveTransition({
|
||||
began: true,
|
||||
cancelled: false,
|
||||
});
|
||||
const percent = this.interactiveTransition?.options.percentFormula ? this.interactiveTransition?.options.percentFormula(args) : args.deltaY / (args.ios.view.bounds.size.height / 2);
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('Interactive dismissal percentage:', percent);
|
||||
}
|
||||
switch (args.state) {
|
||||
case GestureStateTypes.began:
|
||||
if (this._closeModalCallback) {
|
||||
this._closeModalCallback();
|
||||
}
|
||||
break;
|
||||
case GestureStateTypes.changed:
|
||||
if (percent < 1) {
|
||||
if (this.transitionInteractiveCtrl) {
|
||||
this.transitionInteractiveCtrl.updateInteractiveTransition(percent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GestureStateTypes.cancelled:
|
||||
case GestureStateTypes.ended:
|
||||
if (this.transitionInteractiveCtrl) {
|
||||
const finishThreshold = isNumber(this.interactiveTransition?.options?.finishThreshold) ? this.interactiveTransition?.options?.finishThreshold : 0.5;
|
||||
if (percent > finishThreshold) {
|
||||
this.transitionInteractiveCtrl.finishInteractiveTransition();
|
||||
} else {
|
||||
this._updateInteractiveTransition({
|
||||
cancelled: true,
|
||||
});
|
||||
this.transitionInteractiveCtrl.cancelInteractiveTransition();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _hideNativeModalView(parent: View, whenClosedCallback: () => void) {
|
||||
if (!parent || !parent.viewController) {
|
||||
Trace.error('Trying to hide modal view but no parent with viewController specified.');
|
||||
@@ -627,10 +581,10 @@ export class View extends ViewCommon implements ViewDefinition {
|
||||
}
|
||||
|
||||
parentController.dismissViewControllerAnimatedCompletion(animated, () => {
|
||||
if (!this.interactiveTransition?.cancelled) {
|
||||
const transitionState = SharedTransition.getState(this.transitionId);
|
||||
if (!transitionState?.interactiveCancelled) {
|
||||
this._transitioningDelegate = null;
|
||||
this.transitionInteractiveCtrl = null;
|
||||
this.off('pan', this._interactiveDismissGesture);
|
||||
// this.off('pan', this._interactiveDismissGesture);
|
||||
if (this._modalAnimatedOptions) {
|
||||
this._modalAnimatedOptions.pop();
|
||||
}
|
||||
@@ -990,13 +944,11 @@ View.prototype._nativeBackgroundState = 'unset';
|
||||
@NativeClass
|
||||
class UIViewControllerTransitioningDelegateImpl extends NSObject implements UIViewControllerTransitioningDelegate {
|
||||
owner: WeakRef<ModalTransition>;
|
||||
ownerView: WeakRef<View>;
|
||||
static ObjCProtocols = [UIViewControllerTransitioningDelegate];
|
||||
|
||||
static initWithOwner(owner: WeakRef<ModalTransition>, ownerView: WeakRef<View>) {
|
||||
static initWithOwner(owner: WeakRef<ModalTransition>) {
|
||||
const delegate = <UIViewControllerTransitioningDelegateImpl>UIViewControllerTransitioningDelegateImpl.new();
|
||||
delegate.owner = owner;
|
||||
delegate.ownerView = ownerView;
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@@ -1019,12 +971,9 @@ class UIViewControllerTransitioningDelegateImpl extends NSObject implements UIVi
|
||||
interactionControllerForDismissal?(animator: UIViewControllerAnimatedTransitioning): UIViewControllerInteractiveTransitioning {
|
||||
const owner = this.owner?.deref();
|
||||
if (owner?.iosInteractionDismiss) {
|
||||
const ownerView = this.ownerView?.deref();
|
||||
if (ownerView) {
|
||||
if (ownerView.interactiveTransition?.began) {
|
||||
ownerView.transitionInteractiveCtrl = owner.iosInteractionDismiss(animator);
|
||||
return ownerView.transitionInteractiveCtrl;
|
||||
}
|
||||
const transitionState = SharedTransition.getState(owner.id);
|
||||
if (transitionState?.interactiveBegan) {
|
||||
return owner.iosInteractionDismiss(animator);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -1037,14 +986,6 @@ class UIViewControllerTransitioningDelegateImpl extends NSObject implements UIVi
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
presentationControllerForPresentedViewControllerPresentingViewControllerSourceViewController?(presented: UIViewController, presenting: UIViewController, source: UIViewController): UIPresentationController {
|
||||
const owner = this.owner?.deref();
|
||||
if (owner?.iosPresentedViewController) {
|
||||
return owner.iosPresentedViewController(presented, presenting, source);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ContainerView extends View {
|
||||
|
||||
@@ -96,7 +96,9 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
public _modalParent: ViewCommon;
|
||||
private _modalContext: any;
|
||||
private _modal: ViewCommon;
|
||||
interactiveTransition: InteractiveTransitionState;
|
||||
|
||||
// any active transition instance id for tracking state
|
||||
transitionId: number;
|
||||
|
||||
private _measuredWidth: number;
|
||||
private _measuredHeight: number;
|
||||
@@ -250,13 +252,6 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
return false;
|
||||
}
|
||||
|
||||
_updateInteractiveTransition(state: InteractiveTransitionState) {
|
||||
this.interactiveTransition = {
|
||||
...(this.interactiveTransition || {}),
|
||||
...state,
|
||||
};
|
||||
}
|
||||
|
||||
_setupAsRootView(context: any): void {
|
||||
super._setupAsRootView(context);
|
||||
if (!this._styleScope) {
|
||||
@@ -423,16 +418,17 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
};
|
||||
|
||||
const whenClosedCallback = () => {
|
||||
if (this.interactiveTransition?.began) {
|
||||
this._updateInteractiveTransition({
|
||||
began: false,
|
||||
const transitionState = SharedTransition.getState(this.transitionId);
|
||||
if (transitionState?.interactiveBegan) {
|
||||
SharedTransition.updateState(this.transitionId, {
|
||||
interactiveBegan: false,
|
||||
});
|
||||
if (!this.interactiveTransition?.cancelled) {
|
||||
if (!transitionState?.interactiveCancelled) {
|
||||
cleanupModalViews();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.interactiveTransition?.cancelled) {
|
||||
if (!transitionState?.interactiveCancelled) {
|
||||
if (typeof options.closeCallback === 'function') {
|
||||
options.closeCallback.apply(undefined, originalArgs);
|
||||
}
|
||||
@@ -441,7 +437,8 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
}
|
||||
};
|
||||
|
||||
if (!this.interactiveTransition?.began) {
|
||||
const transitionState = SharedTransition.getState(this.transitionId);
|
||||
if (!transitionState?.interactiveBegan) {
|
||||
cleanupModalViews();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,6 @@ export function _clearEntry(entry: BackstackEntry): void;
|
||||
* in order to reapply them when new fragment is created for the same entry.
|
||||
*/
|
||||
export function _clearFragment(entry: BackstackEntry): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _createIOSAnimatedTransitioning(navigationTransition: NavigationTransition, nativeCurve: any, operation: number, fromVC: any, toVC: any): any;
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import { NavigationTransition } from '.';
|
||||
import { Transition } from '../transition';
|
||||
import { SlideTransition } from '../transition/slide-transition';
|
||||
import { FadeTransition } from '../transition/fade-transition';
|
||||
|
||||
import { Trace } from '../../trace';
|
||||
|
||||
@NativeClass
|
||||
class AnimatedTransitioning extends NSObject implements UIViewControllerAnimatedTransitioning {
|
||||
public static ObjCProtocols = [UIViewControllerAnimatedTransitioning];
|
||||
|
||||
private _transition: Transition;
|
||||
private _operation: UINavigationControllerOperation;
|
||||
private _fromVC: UIViewController;
|
||||
private _toVC: UIViewController;
|
||||
private _transitionType: string;
|
||||
|
||||
public static init(transition: Transition, operation: UINavigationControllerOperation, fromVC: UIViewController, toVC: UIViewController): AnimatedTransitioning {
|
||||
const impl = <AnimatedTransitioning>AnimatedTransitioning.new();
|
||||
impl._transition = transition;
|
||||
impl._operation = operation;
|
||||
impl._fromVC = fromVC;
|
||||
impl._toVC = toVC;
|
||||
|
||||
return impl;
|
||||
}
|
||||
|
||||
public animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
switch (this._operation) {
|
||||
case UINavigationControllerOperation.Push:
|
||||
this._transitionType = 'push';
|
||||
break;
|
||||
case UINavigationControllerOperation.Pop:
|
||||
this._transitionType = 'pop';
|
||||
break;
|
||||
case UINavigationControllerOperation.None:
|
||||
this._transitionType = 'none';
|
||||
break;
|
||||
}
|
||||
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`START ${this._transition} ${this._transitionType}`, Trace.categories.Transition);
|
||||
}
|
||||
this._transition.animateIOSTransition(transitionContext, this._fromVC, this._toVC, this._operation);
|
||||
}
|
||||
|
||||
public transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
|
||||
return this._transition.getDuration();
|
||||
}
|
||||
|
||||
public animationEnded(transitionCompleted: boolean): void {
|
||||
if (transitionCompleted) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`END ${this._transition} ${this._transitionType}`, Trace.categories.Transition);
|
||||
}
|
||||
} else {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`CANCEL ${this._transition} ${this._transitionType}`, Trace.categories.Transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function _createIOSAnimatedTransitioning(navigationTransition: NavigationTransition, nativeCurve: UIViewAnimationCurve, operation: UINavigationControllerOperation, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
|
||||
const instance = <Transition>navigationTransition.instance;
|
||||
let transition: Transition;
|
||||
|
||||
if (instance) {
|
||||
// Instance transition should take precedence even if the given name match existing transition.
|
||||
transition = instance;
|
||||
} else if (navigationTransition.name) {
|
||||
const name = navigationTransition.name.toLowerCase();
|
||||
if (name.indexOf('slide') === 0) {
|
||||
const direction = name.substring('slide'.length) || 'left'; //Extract the direction from the string
|
||||
transition = new SlideTransition(direction, navigationTransition.duration, nativeCurve);
|
||||
} else if (name === 'fade') {
|
||||
transition = new FadeTransition(navigationTransition.duration, nativeCurve);
|
||||
}
|
||||
}
|
||||
|
||||
return transition ? AnimatedTransitioning.init(transition, operation, fromVC, toVC) : undefined;
|
||||
}
|
||||
@@ -3,16 +3,14 @@ import { iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition }
|
||||
import { FrameBase, NavigationType } from './frame-common';
|
||||
import { Page } from '../page';
|
||||
import { View } from '../core/view';
|
||||
import { _createIOSAnimatedTransitioning } from './fragment.transitions';
|
||||
import { IOSHelper } from '../core/view/view-helper';
|
||||
import { Screen } from '../../platform';
|
||||
import { profile } from '../../profiling';
|
||||
import { iOSNativeHelper, layout } from '../../utils';
|
||||
import { isNumber } from '../../utils/types';
|
||||
import { Trace } from '../../trace';
|
||||
import type { PageTransition } from '../transition/page-transition';
|
||||
import { SlideTransition } from '../transition/slide-transition';
|
||||
import { FadeTransition } from '../transition/fade-transition';
|
||||
import { SharedTransition } from '../transition/shared-transition';
|
||||
import { GestureStateTypes, PanGestureEventData } from '../gestures';
|
||||
|
||||
export * from './frame-common';
|
||||
|
||||
@@ -29,9 +27,7 @@ let navDepth = -1;
|
||||
export class Frame extends FrameBase {
|
||||
viewController: UINavigationControllerImpl;
|
||||
_animatedDelegate: UINavigationControllerDelegate;
|
||||
transitionInteractiveCtrl: UIPercentDrivenInteractiveTransition;
|
||||
public _ios: iOSFrame;
|
||||
private _interactiveDismissGesture: (args: PanGestureEventData) => void;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -110,15 +106,16 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
this._ios.controller.delegate = this._animatedDelegate;
|
||||
viewController[DELEGATE] = this._animatedDelegate;
|
||||
const transitionState = SharedTransition.getState(navigationTransition.instance.id);
|
||||
if (transitionState?.interactive?.dismiss) {
|
||||
this._updateInteractiveTransition({
|
||||
options: transitionState?.interactive?.dismiss,
|
||||
});
|
||||
// interactive transitions via gestures
|
||||
// TODO - allow users to define their own custom gesture dismissals
|
||||
this._interactiveDismissGesture = this._interactiveDismissGestureHandler.bind(this);
|
||||
this.on('pan', this._interactiveDismissGesture);
|
||||
if (navigationTransition.instance) {
|
||||
this.transitionId = navigationTransition.instance.id;
|
||||
const transitionState = SharedTransition.getState(this.transitionId);
|
||||
if (transitionState?.interactive?.dismiss) {
|
||||
// interactive transitions via gestures
|
||||
// TODO - allow users to define their own custom gesture dismissals
|
||||
navigationTransition.instance.setupInteractiveGesture(() => {
|
||||
this._ios.controller.popViewControllerAnimated(true);
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewController[DELEGATE] = null;
|
||||
@@ -203,48 +200,6 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
}
|
||||
|
||||
private _interactiveDismissGestureHandler(args: PanGestureEventData) {
|
||||
if (args?.ios?.view) {
|
||||
this._updateInteractiveTransition({
|
||||
began: true,
|
||||
cancelled: false,
|
||||
});
|
||||
const percent = this.interactiveTransition?.options.percentFormula ? this.interactiveTransition?.options.percentFormula(args) : args.deltaX / (args.ios.view.bounds.size.width / 2);
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('Interactive dismissal percentage:', percent);
|
||||
}
|
||||
switch (args.state) {
|
||||
case GestureStateTypes.began:
|
||||
this._ios.controller.popViewControllerAnimated(true);
|
||||
break;
|
||||
case GestureStateTypes.changed:
|
||||
if (percent < 1) {
|
||||
if (this.transitionInteractiveCtrl) {
|
||||
this.transitionInteractiveCtrl.updateInteractiveTransition(percent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GestureStateTypes.cancelled:
|
||||
case GestureStateTypes.ended:
|
||||
if (this.transitionInteractiveCtrl) {
|
||||
const finishThreshold = isNumber(this.interactiveTransition?.options?.finishThreshold) ? this.interactiveTransition?.options?.finishThreshold : 0.5;
|
||||
if (percent > finishThreshold) {
|
||||
this.off('pan', this._interactiveDismissGesture);
|
||||
this.transitionInteractiveCtrl.finishInteractiveTransition();
|
||||
// TODO: may wanna null this out at some later point
|
||||
// this.transitionInteractiveCtrl = null;
|
||||
} else {
|
||||
this._updateInteractiveTransition({
|
||||
cancelled: true,
|
||||
});
|
||||
this.transitionInteractiveCtrl.cancelInteractiveTransition();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private pushViewControllerAnimated(viewController: UIViewController, animated: boolean, isModal: boolean) {
|
||||
const transitionCoordinator = this._ios.controller.transitionCoordinator;
|
||||
if (!isModal && transitionCoordinator) {
|
||||
@@ -473,21 +428,35 @@ class UINavigationControllerAnimatedDelegate extends NSObject implements UINavig
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`UINavigationControllerImpl.navigationControllerAnimationControllerForOperationFromViewControllerToViewController(${operation}, ${fromVC}, ${toVC}), transition: ${JSON.stringify(navigationTransition)}`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
this.transition = navigationTransition.instance;
|
||||
|
||||
const curve = _getNativeCurve(navigationTransition);
|
||||
const animationController = _createIOSAnimatedTransitioning(navigationTransition, curve, operation, fromVC, toVC);
|
||||
if (!this.transition) {
|
||||
if (navigationTransition.name) {
|
||||
const curve = _getNativeCurve(navigationTransition);
|
||||
const name = navigationTransition.name.toLowerCase();
|
||||
if (name.indexOf('slide') === 0) {
|
||||
const direction = name.substring('slide'.length) || 'left'; //Extract the direction from the string
|
||||
this.transition = new SlideTransition(direction, navigationTransition.duration, curve);
|
||||
} else if (name === 'fade') {
|
||||
this.transition = new FadeTransition(navigationTransition.duration, curve);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return animationController;
|
||||
if (this.transition?.iosNavigatedController) {
|
||||
return this.transition.iosNavigatedController(navigationController, operation, fromVC, toVC);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
navigationControllerInteractionControllerForAnimationController(navigationController: UINavigationController, animationController: UIViewControllerAnimatedTransitioning): UIViewControllerInteractiveTransitioning {
|
||||
const owner = this.owner?.deref();
|
||||
if (owner) {
|
||||
if (owner.interactiveTransition?.began) {
|
||||
owner.transitionInteractiveCtrl = PercentInteractiveController.initWithOwner(new WeakRef(this.transition));
|
||||
return owner.transitionInteractiveCtrl;
|
||||
const state = SharedTransition.getState(owner.transitionId);
|
||||
if (state?.instance?.iosInteractionDismiss) {
|
||||
if (state?.interactiveBegan) {
|
||||
return state?.instance?.iosInteractionDismiss(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,134 +464,6 @@ class UINavigationControllerAnimatedDelegate extends NSObject implements UINavig
|
||||
}
|
||||
}
|
||||
|
||||
@NativeClass()
|
||||
class PercentInteractiveController extends UIPercentDrivenInteractiveTransition implements UIViewControllerInteractiveTransitioning {
|
||||
static ObjCProtocols = [UIViewControllerInteractiveTransitioning];
|
||||
owner: WeakRef<PageTransition>;
|
||||
started = false;
|
||||
transitionContext: UIViewControllerContextTransitioning;
|
||||
backgroundAnimation: UIViewPropertyAnimator;
|
||||
|
||||
static initWithOwner(owner: WeakRef<PageTransition>) {
|
||||
const ctrl = <PercentInteractiveController>PercentInteractiveController.new();
|
||||
ctrl.owner = owner;
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning) {
|
||||
console.log('startInteractiveTransition');
|
||||
this.transitionContext = transitionContext;
|
||||
|
||||
// Using insertSubviewBelowSubview solves view stack visually but leaves a controller overlapping
|
||||
// TODO: need to find out solution here
|
||||
// console.log('this.transitionContext.containerView.subviews.count:', this.transitionContext.containerView.subviews.count)
|
||||
const owner: any = this.owner?.deref();
|
||||
if (owner) {
|
||||
// console.log('owner.presenting:', owner.presenting)
|
||||
// console.log('owner.presented:', owner.presented)
|
||||
if (owner.presenting) {
|
||||
this.transitionContext.containerView.insertSubviewBelowSubview(owner.presenting.view, owner.presented.view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateInteractiveTransition(percentComplete: number) {
|
||||
const owner: any = this.owner?.deref();
|
||||
if (owner) {
|
||||
if (!this.started) {
|
||||
this.started = true;
|
||||
for (const p of owner.sharedElements.presented) {
|
||||
p.view.opacity = 0;
|
||||
}
|
||||
for (const p of owner.sharedElements.presenting) {
|
||||
p.snapshot.alpha = p.endOpacity;
|
||||
this.transitionContext.containerView.addSubview(p.snapshot);
|
||||
}
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
const props = state.fromPageEnd;
|
||||
this.backgroundAnimation = UIViewPropertyAnimator.alloc().initWithDurationDampingRatioAnimations(1, 1, () => {
|
||||
for (const p of owner.sharedElements.presenting) {
|
||||
p.snapshot.frame = p.startFrame;
|
||||
iOSNativeHelper.copyLayerProperties(p.snapshot, p.view.ios);
|
||||
|
||||
p.snapshot.alpha = 1;
|
||||
}
|
||||
owner.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 0;
|
||||
const endX = isNumber(props?.x) ? props?.x : Screen.mainScreen.widthDIPs;
|
||||
const endY = isNumber(props?.y) ? props?.y : 0;
|
||||
const endWidth = isNumber(props?.width) ? props?.width : Screen.mainScreen.widthDIPs;
|
||||
const endHeight = isNumber(props?.height) ? props?.height : Screen.mainScreen.heightDIPs;
|
||||
owner.presented.view.frame = CGRectMake(endX, endY, endWidth, endHeight);
|
||||
});
|
||||
}
|
||||
|
||||
this.backgroundAnimation.fractionComplete = percentComplete;
|
||||
}
|
||||
}
|
||||
|
||||
cancelInteractiveTransition() {
|
||||
// console.log('cancelInteractiveTransition');
|
||||
const owner: any = this.owner?.deref();
|
||||
if (owner && this.started) {
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
if (this.backgroundAnimation) {
|
||||
this.backgroundAnimation.reversed = true;
|
||||
const duration = isNumber(state.toPageStart?.duration) ? state.toPageStart?.duration / 1000 : 0.35;
|
||||
this.backgroundAnimation.continueAnimationWithTimingParametersDurationFactor(null, duration);
|
||||
setTimeout(() => {
|
||||
for (const p of owner.sharedElements.presented) {
|
||||
p.view.opacity = 1;
|
||||
}
|
||||
for (const p of owner.sharedElements.presenting) {
|
||||
p.snapshot.removeFromSuperview();
|
||||
}
|
||||
owner.presented.view.alpha = 1;
|
||||
this.backgroundAnimation = null;
|
||||
this.started = false;
|
||||
this.transitionContext.completeTransition(false);
|
||||
}, duration * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finishInteractiveTransition() {
|
||||
// console.log('finishInteractiveTransition');
|
||||
const owner: any = this.owner?.deref();
|
||||
if (owner && this.started) {
|
||||
if (this.backgroundAnimation) {
|
||||
this.backgroundAnimation.reversed = false;
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
if (!state) {
|
||||
SharedTransition.finishState(owner.id);
|
||||
this.transitionContext.completeTransition(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = isNumber(state.fromPageEnd?.duration) ? state.fromPageEnd?.duration / 1000 : 0.35;
|
||||
this.backgroundAnimation.continueAnimationWithTimingParametersDurationFactor(null, duration);
|
||||
setTimeout(() => {
|
||||
for (const presenting of owner.sharedElements.presenting) {
|
||||
presenting.view.opacity = presenting.startOpacity;
|
||||
presenting.snapshot.removeFromSuperview();
|
||||
}
|
||||
|
||||
SharedTransition.finishState(owner.id);
|
||||
this.backgroundAnimation = null;
|
||||
this.started = false;
|
||||
// console.log('this.transitionContext.containerView.subviews.count:', this.transitionContext.containerView.subviews.count)
|
||||
// this.transitionContext.containerView.subviews.objectAtIndex(this.transitionContext.containerView.subviews.count-1).removeFromSuperview();
|
||||
this.transitionContext.completeTransition(true);
|
||||
console.log('completeTransition!');
|
||||
// console.log('this.transitionContext.containerView.subviews.count:', this.transitionContext.containerView.subviews.count)
|
||||
}, duration * 1001);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NativeClass
|
||||
class UINavigationControllerImpl extends UINavigationController {
|
||||
private _owner: WeakRef<Frame>;
|
||||
|
||||
@@ -77,6 +77,8 @@ export { TimePicker } from './time-picker';
|
||||
export { Transition } from './transition';
|
||||
export { ModalTransition } from './transition/modal-transition';
|
||||
export { PageTransition } from './transition/page-transition';
|
||||
export { FadeTransition } from './transition/fade-transition';
|
||||
export { SlideTransition } from './transition/slide-transition';
|
||||
export { SharedTransition, SharedTransitionAnimationType } from './transition/shared-transition';
|
||||
export { WebView } from './web-view';
|
||||
export type { LoadEventData, WebViewNavigationType } from './web-view';
|
||||
|
||||
@@ -1,38 +1,77 @@
|
||||
import { Transition } from '.';
|
||||
import { DEFAULT_DURATION } from './shared-transition';
|
||||
|
||||
export class FadeTransition extends Transition {
|
||||
public animateIOSTransition(transitionContext: UIViewControllerContextTransitioning, fromViewCtrl: UIViewController, toViewCtrl: UIViewController, operation: UINavigationControllerOperation): void {
|
||||
const toView = toViewCtrl.view;
|
||||
const originalToViewAlpha = toView.alpha;
|
||||
const fromView = fromViewCtrl.view;
|
||||
const originalFromViewAlpha = fromView.alpha;
|
||||
transitionController: FadeTransitionController;
|
||||
presented: UIViewController;
|
||||
presenting: UIViewController;
|
||||
operation: number;
|
||||
|
||||
toView.alpha = 0.0;
|
||||
fromView.alpha = 1.0;
|
||||
|
||||
switch (operation) {
|
||||
case UINavigationControllerOperation.Push:
|
||||
transitionContext.containerView.insertSubviewAboveSubview(toView, fromView);
|
||||
break;
|
||||
case UINavigationControllerOperation.Pop:
|
||||
transitionContext.containerView.insertSubviewBelowSubview(toView, fromView);
|
||||
break;
|
||||
}
|
||||
|
||||
const duration = this.getDuration();
|
||||
const curve = this.getCurve();
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
duration,
|
||||
() => {
|
||||
UIView.setAnimationCurve(curve);
|
||||
toView.alpha = 1.0;
|
||||
fromView.alpha = 0.0;
|
||||
},
|
||||
(finished: boolean) => {
|
||||
toView.alpha = originalToViewAlpha;
|
||||
fromView.alpha = originalFromViewAlpha;
|
||||
transitionContext.completeTransition(finished);
|
||||
}
|
||||
);
|
||||
iosNavigatedController(navigationController: UINavigationController, operation: number, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
|
||||
this.transitionController = FadeTransitionController.initWithOwner(new WeakRef(this));
|
||||
this.presented = toVC;
|
||||
this.presenting = fromVC;
|
||||
this.operation = operation;
|
||||
console.log('presenting:', this.presenting);
|
||||
return this.transitionController;
|
||||
}
|
||||
}
|
||||
|
||||
@NativeClass()
|
||||
export class FadeTransitionController extends NSObject implements UIViewControllerAnimatedTransitioning {
|
||||
static ObjCProtocols = [UIViewControllerAnimatedTransitioning];
|
||||
owner: WeakRef<FadeTransition>;
|
||||
|
||||
static initWithOwner(owner: WeakRef<FadeTransition>) {
|
||||
const ctrl = <FadeTransitionController>FadeTransitionController.new();
|
||||
ctrl.owner = owner;
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
return owner.getDuration();
|
||||
}
|
||||
return DEFAULT_DURATION;
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
console.log('FadeTransitionController animateTransition:', owner.operation);
|
||||
const toView = owner.presented.view;
|
||||
const originalToViewAlpha = toView.alpha;
|
||||
const fromView = owner.presenting.view;
|
||||
const originalFromViewAlpha = fromView.alpha;
|
||||
|
||||
toView.alpha = 0.0;
|
||||
fromView.alpha = 1.0;
|
||||
|
||||
switch (owner.operation) {
|
||||
case UINavigationControllerOperation.Push:
|
||||
transitionContext.containerView.insertSubviewAboveSubview(toView, fromView);
|
||||
break;
|
||||
case UINavigationControllerOperation.Pop:
|
||||
transitionContext.containerView.insertSubviewBelowSubview(toView, fromView);
|
||||
break;
|
||||
}
|
||||
|
||||
const duration = owner.getDuration();
|
||||
const curve = owner.getCurve();
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
duration,
|
||||
() => {
|
||||
UIView.setAnimationCurve(curve);
|
||||
toView.alpha = 1.0;
|
||||
fromView.alpha = 0.0;
|
||||
},
|
||||
(finished: boolean) => {
|
||||
toView.alpha = originalToViewAlpha;
|
||||
fromView.alpha = originalFromViewAlpha;
|
||||
transitionContext.completeTransition(finished);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
packages/core/ui/transition/index.d.ts
vendored
5
packages/core/ui/transition/index.d.ts
vendored
@@ -8,6 +8,9 @@ export declare class Transition {
|
||||
getCurve(): any;
|
||||
animateIOSTransition(transitionContext: any /*UIViewControllerContextTransitioning */, fromViewCtrl: any /* UIViewController */, toViewCtrl: any /* UIViewController */, operation: any /* UINavigationControllerOperation */): void;
|
||||
createAndroidAnimator(transitionType: string): any;
|
||||
|
||||
setupInteractiveGesture?(startCallback: () => void, view: View): void;
|
||||
|
||||
iosDismissedController?(dismissed: any /* UIViewController */): any /* UIViewControllerAnimatedTransitioning */;
|
||||
|
||||
iosPresentedController?(presented: any /* UIViewController */, presenting: any /* UIViewController */, source: any /* UIViewController */): any /* UIViewControllerAnimatedTransitioning */;
|
||||
@@ -16,7 +19,7 @@ export declare class Transition {
|
||||
|
||||
iosInteractionPresented?(animator: any /* UIViewControllerAnimatedTransitioning */): any /* UIViewControllerInteractiveTransitioning */;
|
||||
|
||||
iosPresentedViewController?(presented: any /* UIViewController */, presenting: any /* UIViewController */, source: any /* UIViewController */): any /* UIPresentationController */;
|
||||
iosNavigatedController?(navigationController: any /* UINavigationController */, operation: number, fromVC: any /* UIViewController */, toVC: any /* UIViewController */): any /* UIViewControllerAnimatedTransitioning */;
|
||||
|
||||
androidFragmentTransactionCallback?(fragmentTransaction: any /* androidx.fragment.app.FragmentTransaction */, currentEntry: BackstackEntry, newEntry: BackstackEntry): void;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Transition as TransitionType } from '.';
|
||||
import { Screen } from '../../platform';
|
||||
|
||||
let transitionId = 0;
|
||||
export class Transition implements TransitionType {
|
||||
|
||||
@@ -4,16 +4,20 @@ import { iOSNativeHelper } from '../../utils/native-helper';
|
||||
import { isNumber } from '../../utils/types';
|
||||
import { Transition } from '.';
|
||||
import { SharedTransition, SharedTransitionAnimationType, DEFAULT_DURATION, DEFAULT_SPRING } from './shared-transition';
|
||||
import { PanGestureEventData, GestureStateTypes } from '../gestures';
|
||||
|
||||
export class ModalTransition extends Transition {
|
||||
transitionController: ModalTransitionController;
|
||||
interactiveController: UIPercentDrivenInteractiveTransition;
|
||||
interactiveGestureRecognizer: UIScreenEdgePanGestureRecognizer;
|
||||
presented: UIViewController;
|
||||
presenting: UIViewController;
|
||||
sharedElements: {
|
||||
presented?: Array<{ view: View; startFrame?: CGRect; endFrame?: CGRect; snapshot?: UIImageView; startOpacity?: number; endOpacity?: number }>;
|
||||
presenting?: Array<{ view: View; startFrame: CGRect; endFrame?: CGRect; snapshot?: UIImageView; startOpacity?: number; endOpacity?: number }>;
|
||||
};
|
||||
private _interactiveStartCallback: () => void;
|
||||
private _interactiveDismissGesture: (args: any /*PanGestureEventData*/) => void;
|
||||
|
||||
iosPresentedController(presented: UIViewController, presenting: UIViewController, source: UIViewController): UIViewControllerAnimatedTransitioning {
|
||||
this.transitionController = ModalTransitionController.initWithOwner(new WeakRef(this));
|
||||
@@ -39,9 +43,60 @@ export class ModalTransition extends Transition {
|
||||
return null;
|
||||
}
|
||||
|
||||
iosPresentedViewController(presented: UIViewController, presenting: UIViewController, source: UIViewController): UIPresentationController {
|
||||
// console.log('-- iosPresentedViewController --');
|
||||
return null;
|
||||
setupInteractiveGesture(startCallback: () => void, view: View) {
|
||||
this._interactiveStartCallback = startCallback;
|
||||
this._interactiveDismissGesture = this._interactiveDismissGestureHandler.bind(this);
|
||||
view.on('pan', this._interactiveDismissGesture);
|
||||
// this.interactiveGestureRecognizer = UIScreenEdgePanGestureRecognizer.alloc().initWithTargetAction()
|
||||
// let edgeSwipeGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
|
||||
// edgeSwipeGestureRecognizer.edges = .left
|
||||
// view.addGestureRecognizer(edgeSwipeGestureRecognizer)
|
||||
}
|
||||
|
||||
private _interactiveDismissGestureHandler(args: PanGestureEventData) {
|
||||
if (args?.ios?.view) {
|
||||
const state = SharedTransition.getState(this.id);
|
||||
// SharedTransition.updateState(this.id, {
|
||||
// interactiveBegan: true,
|
||||
// interactiveCancelled: false
|
||||
// });
|
||||
const percent = state.interactive?.dismiss?.percentFormula ? state.interactive.dismiss.percentFormula(args) : args.deltaY / (args.ios.view.bounds.size.height / 2);
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('Interactive dismissal percentage:', percent);
|
||||
}
|
||||
switch (args.state) {
|
||||
case GestureStateTypes.began:
|
||||
SharedTransition.updateState(this.id, {
|
||||
interactiveBegan: true,
|
||||
interactiveCancelled: false,
|
||||
});
|
||||
if (this._interactiveStartCallback) {
|
||||
this._interactiveStartCallback();
|
||||
}
|
||||
break;
|
||||
case GestureStateTypes.changed:
|
||||
if (percent < 1) {
|
||||
if (this.interactiveController) {
|
||||
this.interactiveController.updateInteractiveTransition(percent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GestureStateTypes.cancelled:
|
||||
case GestureStateTypes.ended:
|
||||
if (this.interactiveController) {
|
||||
const finishThreshold = isNumber(state.interactive?.dismiss?.finishThreshold) ? state.interactive.dismiss.finishThreshold : 0.5;
|
||||
if (percent > finishThreshold) {
|
||||
this.interactiveController.finishInteractiveTransition();
|
||||
} else {
|
||||
SharedTransition.updateState(this.id, {
|
||||
interactiveCancelled: true,
|
||||
});
|
||||
this.interactiveController.cancelInteractiveTransition();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +183,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition
|
||||
const owner = this.owner?.deref();
|
||||
if (owner && this.started) {
|
||||
if (this.backgroundAnimation) {
|
||||
this.backgroundAnimation.reversed = false;
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
if (!state) {
|
||||
SharedTransition.finishState(owner.id);
|
||||
@@ -165,7 +221,7 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
// console.log('ModalTransitionController animateTransition:', animationType);
|
||||
// console.log('ModalTransitionController animateTransition');
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
// console.log('owner.id:', owner.id);
|
||||
|
||||
@@ -3,294 +3,532 @@ import { Transition } from '.';
|
||||
import { Screen } from '../../platform';
|
||||
import { iOSNativeHelper } from '../../utils/native-helper';
|
||||
import { isNumber } from '../../utils/types';
|
||||
import { PanGestureEventData, GestureStateTypes } from '../gestures';
|
||||
import { SharedTransition, SharedTransitionAnimationType, DEFAULT_DURATION, DEFAULT_SPRING } from './shared-transition';
|
||||
|
||||
export class PageTransition extends Transition {
|
||||
transitionController: PageTransitionController;
|
||||
interactiveController: UIPercentDrivenInteractiveTransition;
|
||||
presented: UIViewController;
|
||||
presenting: UIViewController;
|
||||
operation: number;
|
||||
sharedElements: {
|
||||
presented?: Array<{ view: View; startFrame?: CGRect; endFrame?: CGRect; snapshot?: UIImageView; startOpacity?: number; endOpacity?: number }>;
|
||||
presenting?: Array<{ view: View; startFrame: CGRect; endFrame?: CGRect; snapshot?: UIImageView; startOpacity?: number; endOpacity?: number }>;
|
||||
};
|
||||
private _interactiveStartCallback: () => void;
|
||||
private _interactiveDismissGesture: (args: any /*PanGestureEventData*/) => void;
|
||||
private _interactiveGestureTeardown: () => void;
|
||||
|
||||
animateIOSTransition(transitionContext: UIViewControllerContextTransitioning, fromViewCtrl: UIViewController, toViewCtrl: UIViewController, operation: UINavigationControllerOperation): void {
|
||||
// console.log('--- PageTransitionController animateTransition');
|
||||
// console.log('toViewCtrl:', toViewCtrl);
|
||||
// console.log('fromViewCtrl:', fromViewCtrl);
|
||||
|
||||
// console.log('owner.id:', owner.id);
|
||||
const state = SharedTransition.getState(this.id);
|
||||
if (!state) {
|
||||
return;
|
||||
iosNavigatedController(navigationController: UINavigationController, operation: number, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
|
||||
if (!this.transitionController) {
|
||||
this.presented = toVC;
|
||||
this.presenting = fromVC;
|
||||
}
|
||||
// console.log('state.activeType:', state.activeType);
|
||||
// console.log('operation:', operation);
|
||||
// switch (state.activeType) {
|
||||
switch (operation) {
|
||||
case UINavigationControllerOperation.Push: {
|
||||
this.presented = toViewCtrl;
|
||||
this.presenting = fromViewCtrl;
|
||||
this.transitionController = PageTransitionController.initWithOwner(new WeakRef(this));
|
||||
console.log('iosNavigatedController presenting:', this.presenting);
|
||||
|
||||
transitionContext.containerView.insertSubviewAboveSubview(this.presented.view, fromViewCtrl.view);
|
||||
this.presented.view.layoutIfNeeded();
|
||||
this.operation = operation;
|
||||
return this.transitionController;
|
||||
}
|
||||
|
||||
const { sharedElements, presented } = SharedTransition.getSharedElements(state.page, state.toPage);
|
||||
iosInteractionDismiss(animator: UIViewControllerAnimatedTransitioning): UIViewControllerInteractiveTransitioning {
|
||||
// console.log('-- iosInteractionDismiss --');
|
||||
this.interactiveController = PercentInteractiveController.initWithOwner(new WeakRef(this));
|
||||
return this.interactiveController;
|
||||
}
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(' PageTransition: Push');
|
||||
console.log(
|
||||
`1. Found sharedTransitionTags to animate:`,
|
||||
sharedElements.map((v) => v.sharedTransitionTag)
|
||||
);
|
||||
setupInteractiveGesture(startCallback: () => void, view: View): () => void {
|
||||
console.log(' -- setupInteractiveGesture --');
|
||||
this._interactiveStartCallback = startCallback;
|
||||
if (!this._interactiveDismissGesture) {
|
||||
console.log('setup but tearing down first!');
|
||||
view.off('pan', this._interactiveDismissGesture);
|
||||
this._interactiveDismissGesture = this._interactiveDismissGestureHandler.bind(this);
|
||||
}
|
||||
view.on('pan', this._interactiveDismissGesture);
|
||||
|
||||
console.log(`2. Take snapshots of shared elements and position them based on presenting view:`);
|
||||
}
|
||||
this._interactiveGestureTeardown = () => {
|
||||
console.log(`-- TEARDOWN setupInteractiveGesture --`);
|
||||
view.off('pan', this._interactiveDismissGesture);
|
||||
this._interactiveDismissGesture = null;
|
||||
};
|
||||
return this._interactiveGestureTeardown;
|
||||
}
|
||||
|
||||
for (const presentingView of sharedElements) {
|
||||
if (!this.sharedElements) {
|
||||
this.sharedElements = {
|
||||
presented: [],
|
||||
presenting: [],
|
||||
};
|
||||
}
|
||||
const presentingSharedElement = presentingView.ios;
|
||||
private _interactiveDismissGestureHandler(args: PanGestureEventData) {
|
||||
if (args?.ios?.view) {
|
||||
// console.log('this.id:', this.id);
|
||||
const state = SharedTransition.getState(this.id);
|
||||
|
||||
// TODO: discuss whether we should check if UIImage/UIImageView type to always snapshot images or if other view types could be duped/added vs. snapshotted
|
||||
// Note: snapshot may be most efficient/simple
|
||||
// console.log('---> ', presentingView.sharedTransitionTag, ': ', presentingSharedElement)
|
||||
|
||||
const presentedView = presented.find((v) => v.sharedTransitionTag === presentingView.sharedTransitionTag);
|
||||
const presentedSharedElement = presentedView.ios;
|
||||
|
||||
const sharedElementSnapshot = UIImageView.alloc().init(); // WithImage(snapshotView(presentedSharedElement));
|
||||
|
||||
// treat images differently...
|
||||
if (presentedSharedElement instanceof UIImageView) {
|
||||
// in case the image is loaded async, we need to update the snapshot when it changes
|
||||
// todo: remove listener on transition end
|
||||
presentedView.on('imageSourceChange', () => {
|
||||
sharedElementSnapshot.image = iOSNativeHelper.snapshotView(presentedSharedElement, Screen.mainScreen.scale);
|
||||
sharedElementSnapshot.tintColor = presentedSharedElement.tintColor;
|
||||
});
|
||||
|
||||
sharedElementSnapshot.tintColor = presentedSharedElement.tintColor;
|
||||
sharedElementSnapshot.contentMode = presentedSharedElement.contentMode;
|
||||
}
|
||||
|
||||
iOSNativeHelper.copyLayerProperties(sharedElementSnapshot, presentingSharedElement);
|
||||
sharedElementSnapshot.clipsToBounds = true;
|
||||
// console.log('---> snapshot: ', sharedElementSnapshot);
|
||||
|
||||
const startFrame = presentingSharedElement.convertRectToView(presentingSharedElement.bounds, transitionContext.containerView);
|
||||
const endFrame = presentedSharedElement.convertRectToView(presentedSharedElement.bounds, transitionContext.containerView);
|
||||
sharedElementSnapshot.frame = startFrame;
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('---> ', presentingView.sharedTransitionTag, ' frame:', iOSNativeHelper.printCGRect(sharedElementSnapshot.frame));
|
||||
}
|
||||
|
||||
this.sharedElements.presenting.push({
|
||||
view: presentingView,
|
||||
startFrame,
|
||||
endFrame,
|
||||
snapshot: sharedElementSnapshot,
|
||||
startOpacity: presentingView.opacity,
|
||||
endOpacity: presentedView.opacity,
|
||||
});
|
||||
this.sharedElements.presented.push({
|
||||
view: presentedView,
|
||||
startFrame: endFrame,
|
||||
endFrame: startFrame,
|
||||
startOpacity: presentedView.opacity,
|
||||
endOpacity: presentingView.opacity,
|
||||
});
|
||||
|
||||
// set initial opacity to match the source view opacity
|
||||
sharedElementSnapshot.alpha = presentingView.opacity;
|
||||
// hide both while animating within the transition context
|
||||
presentingView.opacity = 0;
|
||||
presentedView.opacity = 0;
|
||||
// add snapshot to animate
|
||||
transitionContext.containerView.addSubview(sharedElementSnapshot);
|
||||
}
|
||||
|
||||
const cleanupPresent = () => {
|
||||
for (const presented of this.sharedElements.presented) {
|
||||
presented.view.opacity = presented.startOpacity;
|
||||
}
|
||||
for (const presenting of this.sharedElements.presenting) {
|
||||
presenting.snapshot.removeFromSuperview();
|
||||
}
|
||||
SharedTransition.updateState(this.id, {
|
||||
activeType: SharedTransitionAnimationType.dismiss,
|
||||
});
|
||||
transitionContext.completeTransition(true);
|
||||
};
|
||||
const updateFramePresent = () => {
|
||||
// https://stackoverflow.com/a/27997678/1418981
|
||||
// In order to have proper layout. Seems mostly needed when presenting.
|
||||
// For instance during presentation, destination view doesn't account navigation bar height.
|
||||
// Not sure if best to leave all the time?
|
||||
// owner.presented.view.setNeedsLayout();
|
||||
// owner.presented.view.layoutIfNeeded();
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('3. Animating shared elements:');
|
||||
}
|
||||
for (const presented of this.sharedElements.presented) {
|
||||
const presentingMatch = this.sharedElements.presenting.find((v) => v.view.sharedTransitionTag === presented.view.sharedTransitionTag);
|
||||
// Workaround wrong origin due ongoing layout process.
|
||||
const updatedEndFrame = presented.view.ios.convertRectToView(presented.view.ios.bounds, transitionContext.containerView);
|
||||
const correctedEndFrame = CGRectMake(updatedEndFrame.origin.x, updatedEndFrame.origin.y, presentingMatch.endFrame.size.width, presentingMatch.endFrame.size.height);
|
||||
presentingMatch.snapshot.frame = correctedEndFrame;
|
||||
|
||||
// apply view and layer properties to the snapshot view to match the source/presented view
|
||||
iOSNativeHelper.copyLayerProperties(presentingMatch.snapshot, presented.view.ios);
|
||||
// create a snapshot of the presented view
|
||||
presentingMatch.snapshot.image = iOSNativeHelper.snapshotView(presented.view.ios, Screen.mainScreen.scale);
|
||||
// apply correct alpha
|
||||
presentingMatch.snapshot.alpha = presentingMatch.endOpacity;
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(`---> ${presentingMatch.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(correctedEndFrame));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// starting page properties
|
||||
const toProps = state.toPageStart;
|
||||
this.presented.view.alpha = isNumber(toProps?.opacity) ? toProps?.opacity : 0;
|
||||
|
||||
const startX = isNumber(toProps?.x) ? toProps?.x : Screen.mainScreen.widthDIPs;
|
||||
const startY = isNumber(toProps?.y) ? toProps?.y : 0;
|
||||
const startWidth = isNumber(toProps?.width) ? toProps?.width : Screen.mainScreen.widthDIPs;
|
||||
const startHeight = isNumber(toProps?.height) ? toProps?.height : Screen.mainScreen.heightDIPs;
|
||||
this.presented.view.frame = CGRectMake(startX, startY, startWidth, startHeight);
|
||||
|
||||
const animateProperties = () => {
|
||||
const props = state.toPageEnd;
|
||||
// animate page properties to the following:
|
||||
this.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 1;
|
||||
|
||||
const endX = isNumber(props?.x) ? props?.x : 0;
|
||||
const endY = isNumber(props?.y) ? props?.y : 0;
|
||||
const endWidth = isNumber(props?.width) ? props?.width : Screen.mainScreen.widthDIPs;
|
||||
const endHeight = isNumber(props?.height) ? props?.height : Screen.mainScreen.heightDIPs;
|
||||
this.presented.view.frame = CGRectMake(endX, endY, endWidth, endHeight);
|
||||
};
|
||||
if (isNumber(toProps?.duration)) {
|
||||
// override spring and use only linear animation
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
toProps?.duration / 1000,
|
||||
() => {
|
||||
animateProperties();
|
||||
updateFramePresent();
|
||||
},
|
||||
() => {
|
||||
cleanupPresent();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const spring = toProps?.spring;
|
||||
iOSNativeHelper.animateWithSpring({
|
||||
tension: isNumber(spring?.tension) ? spring?.tension : DEFAULT_SPRING.tension,
|
||||
friction: isNumber(spring?.friction) ? spring?.tension : DEFAULT_SPRING.friction,
|
||||
animations: () => {
|
||||
animateProperties();
|
||||
|
||||
updateFramePresent();
|
||||
},
|
||||
completion: () => {
|
||||
cleanupPresent();
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
const percent = state.interactive?.dismiss?.percentFormula ? state.interactive.dismiss.percentFormula(args) : args.deltaX / (args.ios.view.bounds.size.width / 2);
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('Interactive dismissal percentage:', percent);
|
||||
}
|
||||
case UINavigationControllerOperation.Pop: {
|
||||
transitionContext.containerView.insertSubviewBelowSubview(toViewCtrl.view, fromViewCtrl.view);
|
||||
|
||||
// console.log('transitionContext.containerView.subviews.count:', transitionContext.containerView.subviews.count);
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(' PageTransition: Pop');
|
||||
console.log(
|
||||
`1. Dismiss sharedTransitionTags to animate:`,
|
||||
this.sharedElements.presented.map((p) => p.view.sharedTransitionTag)
|
||||
);
|
||||
|
||||
console.log(`2. Add back previously stored sharedElements to dismiss:`);
|
||||
}
|
||||
|
||||
for (const p of this.sharedElements.presented) {
|
||||
p.view.opacity = 0;
|
||||
}
|
||||
for (const p of this.sharedElements.presenting) {
|
||||
p.snapshot.alpha = p.endOpacity;
|
||||
transitionContext.containerView.addSubview(p.snapshot);
|
||||
}
|
||||
|
||||
const cleanupDismiss = () => {
|
||||
for (const presenting of this.sharedElements.presenting) {
|
||||
presenting.view.opacity = presenting.startOpacity;
|
||||
presenting.snapshot.removeFromSuperview();
|
||||
}
|
||||
SharedTransition.finishState(this.id);
|
||||
transitionContext.completeTransition(true);
|
||||
};
|
||||
const updateFrameDismiss = () => {
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('3. Dismissing shared elements:');
|
||||
}
|
||||
for (const presenting of this.sharedElements.presenting) {
|
||||
iOSNativeHelper.copyLayerProperties(presenting.snapshot, presenting.view.ios);
|
||||
presenting.snapshot.frame = presenting.startFrame;
|
||||
presenting.snapshot.alpha = presenting.startOpacity;
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(`---> ${presenting.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(presenting.startFrame));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const props = state.fromPageEnd;
|
||||
|
||||
const animateProperties = () => {
|
||||
this.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 0;
|
||||
|
||||
const endX = isNumber(props?.x) ? props?.x : Screen.mainScreen.widthDIPs;
|
||||
const endY = isNumber(props?.y) ? props?.y : 0;
|
||||
const endWidth = isNumber(props?.width) ? props?.width : Screen.mainScreen.widthDIPs;
|
||||
const endHeight = isNumber(props?.height) ? props?.height : Screen.mainScreen.heightDIPs;
|
||||
this.presented.view.frame = CGRectMake(endX, endY, endWidth, endHeight);
|
||||
};
|
||||
|
||||
if (isNumber(props?.duration)) {
|
||||
// override spring and use only linear animation
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
props?.duration / 1000,
|
||||
() => {
|
||||
animateProperties();
|
||||
updateFrameDismiss();
|
||||
},
|
||||
() => {
|
||||
cleanupDismiss();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const spring = props?.spring;
|
||||
iOSNativeHelper.animateWithSpring({
|
||||
tension: isNumber(spring?.tension) ? spring?.tension : DEFAULT_SPRING.tension,
|
||||
friction: isNumber(spring?.friction) ? spring?.tension : DEFAULT_SPRING.friction,
|
||||
animations: () => {
|
||||
animateProperties();
|
||||
|
||||
updateFrameDismiss();
|
||||
},
|
||||
completion: () => {
|
||||
cleanupDismiss();
|
||||
},
|
||||
switch (args.state) {
|
||||
case GestureStateTypes.began:
|
||||
SharedTransition.updateState(this.id, {
|
||||
interactiveBegan: true,
|
||||
interactiveCancelled: false,
|
||||
});
|
||||
}
|
||||
break;
|
||||
if (this._interactiveStartCallback) {
|
||||
this._interactiveStartCallback();
|
||||
}
|
||||
break;
|
||||
case GestureStateTypes.changed:
|
||||
if (percent < 1) {
|
||||
if (this.interactiveController) {
|
||||
this.interactiveController.updateInteractiveTransition(percent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GestureStateTypes.cancelled:
|
||||
case GestureStateTypes.ended:
|
||||
if (this.interactiveController) {
|
||||
const finishThreshold = isNumber(state.interactive?.dismiss?.finishThreshold) ? state.interactive.dismiss.finishThreshold : 0.5;
|
||||
if (percent > finishThreshold) {
|
||||
if (this._interactiveGestureTeardown) {
|
||||
this._interactiveGestureTeardown();
|
||||
this._interactiveGestureTeardown = null;
|
||||
}
|
||||
this.interactiveController.finishInteractiveTransition();
|
||||
} else {
|
||||
SharedTransition.updateState(this.id, {
|
||||
interactiveCancelled: true,
|
||||
});
|
||||
this.interactiveController.cancelInteractiveTransition();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NativeClass()
|
||||
class PercentInteractiveController extends UIPercentDrivenInteractiveTransition implements UIViewControllerInteractiveTransitioning {
|
||||
static ObjCProtocols = [UIViewControllerInteractiveTransitioning];
|
||||
owner: WeakRef<PageTransition>;
|
||||
started = false;
|
||||
transitionContext: UIViewControllerContextTransitioning;
|
||||
backgroundAnimation: UIViewPropertyAnimator;
|
||||
|
||||
static initWithOwner(owner: WeakRef<PageTransition>) {
|
||||
const ctrl = <PercentInteractiveController>PercentInteractiveController.new();
|
||||
ctrl.owner = owner;
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning) {
|
||||
console.log('startInteractiveTransition');
|
||||
this.transitionContext = transitionContext;
|
||||
|
||||
const owner: any = this.owner?.deref();
|
||||
if (owner) {
|
||||
this.transitionContext.containerView.insertSubviewBelowSubview(owner.presenting.view, owner.presented.view);
|
||||
}
|
||||
}
|
||||
|
||||
updateInteractiveTransition(percentComplete: number) {
|
||||
const owner: any = this.owner?.deref();
|
||||
if (owner) {
|
||||
if (!this.started) {
|
||||
this.started = true;
|
||||
for (const p of owner.sharedElements.presented) {
|
||||
p.view.opacity = 0;
|
||||
}
|
||||
for (const p of owner.sharedElements.presenting) {
|
||||
p.snapshot.alpha = p.endOpacity;
|
||||
this.transitionContext.containerView.addSubview(p.snapshot);
|
||||
}
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
const props = state.fromPageEnd;
|
||||
this.backgroundAnimation = UIViewPropertyAnimator.alloc().initWithDurationDampingRatioAnimations(1, 1, () => {
|
||||
for (const p of owner.sharedElements.presenting) {
|
||||
p.snapshot.frame = p.startFrame;
|
||||
iOSNativeHelper.copyLayerProperties(p.snapshot, p.view.ios);
|
||||
|
||||
p.snapshot.alpha = 1;
|
||||
}
|
||||
owner.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 0;
|
||||
const endX = isNumber(props?.x) ? props?.x : Screen.mainScreen.widthDIPs;
|
||||
const endY = isNumber(props?.y) ? props?.y : 0;
|
||||
const endWidth = isNumber(props?.width) ? props?.width : Screen.mainScreen.widthDIPs;
|
||||
const endHeight = isNumber(props?.height) ? props?.height : Screen.mainScreen.heightDIPs;
|
||||
owner.presented.view.frame = CGRectMake(endX, endY, endWidth, endHeight);
|
||||
});
|
||||
}
|
||||
|
||||
this.backgroundAnimation.fractionComplete = percentComplete;
|
||||
}
|
||||
}
|
||||
|
||||
cancelInteractiveTransition() {
|
||||
// console.log('cancelInteractiveTransition');
|
||||
const owner: any = this.owner?.deref();
|
||||
if (owner && this.started) {
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
if (this.backgroundAnimation) {
|
||||
this.backgroundAnimation.reversed = true;
|
||||
const duration = isNumber(state.toPageStart?.duration) ? state.toPageStart?.duration / 1000 : 0.35;
|
||||
this.backgroundAnimation.continueAnimationWithTimingParametersDurationFactor(null, duration);
|
||||
setTimeout(() => {
|
||||
for (const p of owner.sharedElements.presented) {
|
||||
p.view.opacity = 1;
|
||||
}
|
||||
for (const p of owner.sharedElements.presenting) {
|
||||
p.snapshot.removeFromSuperview();
|
||||
}
|
||||
owner.presented.view.alpha = 1;
|
||||
this.backgroundAnimation = null;
|
||||
this.started = false;
|
||||
this.transitionContext.completeTransition(false);
|
||||
}, duration * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finishInteractiveTransition() {
|
||||
// console.log('finishInteractiveTransition');
|
||||
const owner: any = this.owner?.deref();
|
||||
if (owner && this.started) {
|
||||
if (this.backgroundAnimation) {
|
||||
this.backgroundAnimation.reversed = false;
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
if (!state) {
|
||||
SharedTransition.finishState(owner.id);
|
||||
this.transitionContext.completeTransition(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = isNumber(state.fromPageEnd?.duration) ? state.fromPageEnd?.duration / 1000 : 0.35;
|
||||
this.backgroundAnimation.continueAnimationWithTimingParametersDurationFactor(null, duration);
|
||||
setTimeout(() => {
|
||||
for (const presenting of owner.sharedElements.presenting) {
|
||||
presenting.view.opacity = presenting.startOpacity;
|
||||
// presenting.snapshot.removeFromSuperview();
|
||||
}
|
||||
|
||||
SharedTransition.finishState(owner.id);
|
||||
// SharedTransition.updateState(owner.id, {
|
||||
// interactiveCancelled: false,
|
||||
// });
|
||||
this.backgroundAnimation = null;
|
||||
this.started = false;
|
||||
// this.transitionContext.containerView.insertSubviewAboveSubview(owner.presenting.view, owner.presented.view);
|
||||
this.transitionContext.completeTransition(true);
|
||||
}, duration * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NativeClass()
|
||||
class PageTransitionController extends NSObject implements UIViewControllerAnimatedTransitioning {
|
||||
static ObjCProtocols = [UIViewControllerAnimatedTransitioning];
|
||||
owner: WeakRef<PageTransition>;
|
||||
|
||||
static initWithOwner(owner: WeakRef<PageTransition>) {
|
||||
const ctrl = <PageTransitionController>PageTransitionController.new();
|
||||
ctrl.owner = owner;
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
return owner.getDuration();
|
||||
}
|
||||
return DEFAULT_DURATION;
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
console.log('--- PageTransitionController animateTransition');
|
||||
console.log('owner.presented:', owner.presented);
|
||||
console.log('owner.presenting:', owner.presenting);
|
||||
|
||||
// console.log('owner.id:', owner.id);
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
console.log('state.activeType:', state.activeType);
|
||||
console.log('operation:', owner.operation);
|
||||
// switch (state.activeType) {
|
||||
switch (owner.operation) {
|
||||
case UINavigationControllerOperation.Push: {
|
||||
console.log('pushing!!');
|
||||
// this.presented = toViewCtrl;
|
||||
// this.presenting = fromViewCtrl;
|
||||
|
||||
transitionContext.containerView.insertSubviewAboveSubview(owner.presented.view, owner.presenting.view);
|
||||
owner.presented.view.layoutIfNeeded();
|
||||
|
||||
const { sharedElements, presented } = SharedTransition.getSharedElements(state.page, state.toPage);
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(' PageTransition: Push');
|
||||
console.log(
|
||||
`1. Found sharedTransitionTags to animate:`,
|
||||
sharedElements.map((v) => v.sharedTransitionTag)
|
||||
);
|
||||
|
||||
console.log(`2. Take snapshots of shared elements and position them based on presenting view:`);
|
||||
}
|
||||
|
||||
for (const presentingView of sharedElements) {
|
||||
if (!owner.sharedElements) {
|
||||
owner.sharedElements = {
|
||||
presented: [],
|
||||
presenting: [],
|
||||
};
|
||||
}
|
||||
const presentingSharedElement = presentingView.ios;
|
||||
|
||||
// TODO: discuss whether we should check if UIImage/UIImageView type to always snapshot images or if other view types could be duped/added vs. snapshotted
|
||||
// Note: snapshot may be most efficient/simple
|
||||
// console.log('---> ', presentingView.sharedTransitionTag, ': ', presentingSharedElement)
|
||||
|
||||
const presentedView = presented.find((v) => v.sharedTransitionTag === presentingView.sharedTransitionTag);
|
||||
const presentedSharedElement = presentedView.ios;
|
||||
|
||||
const sharedElementSnapshot = UIImageView.alloc().init(); // WithImage(snapshotView(presentedSharedElement));
|
||||
|
||||
// treat images differently...
|
||||
if (presentedSharedElement instanceof UIImageView) {
|
||||
// in case the image is loaded async, we need to update the snapshot when it changes
|
||||
// todo: remove listener on transition end
|
||||
presentedView.on('imageSourceChange', () => {
|
||||
sharedElementSnapshot.image = iOSNativeHelper.snapshotView(presentedSharedElement, Screen.mainScreen.scale);
|
||||
sharedElementSnapshot.tintColor = presentedSharedElement.tintColor;
|
||||
});
|
||||
|
||||
sharedElementSnapshot.tintColor = presentedSharedElement.tintColor;
|
||||
sharedElementSnapshot.contentMode = presentedSharedElement.contentMode;
|
||||
}
|
||||
|
||||
iOSNativeHelper.copyLayerProperties(sharedElementSnapshot, presentingSharedElement);
|
||||
sharedElementSnapshot.clipsToBounds = true;
|
||||
// console.log('---> snapshot: ', sharedElementSnapshot);
|
||||
|
||||
const startFrame = presentingSharedElement.convertRectToView(presentingSharedElement.bounds, transitionContext.containerView);
|
||||
const endFrame = presentedSharedElement.convertRectToView(presentedSharedElement.bounds, transitionContext.containerView);
|
||||
sharedElementSnapshot.frame = startFrame;
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('---> ', presentingView.sharedTransitionTag, ' frame:', iOSNativeHelper.printCGRect(sharedElementSnapshot.frame));
|
||||
}
|
||||
|
||||
owner.sharedElements.presenting.push({
|
||||
view: presentingView,
|
||||
startFrame,
|
||||
endFrame,
|
||||
snapshot: sharedElementSnapshot,
|
||||
startOpacity: presentingView.opacity,
|
||||
endOpacity: presentedView.opacity,
|
||||
});
|
||||
owner.sharedElements.presented.push({
|
||||
view: presentedView,
|
||||
startFrame: endFrame,
|
||||
endFrame: startFrame,
|
||||
startOpacity: presentedView.opacity,
|
||||
endOpacity: presentingView.opacity,
|
||||
});
|
||||
|
||||
// set initial opacity to match the source view opacity
|
||||
sharedElementSnapshot.alpha = presentingView.opacity;
|
||||
// hide both while animating within the transition context
|
||||
presentingView.opacity = 0;
|
||||
presentedView.opacity = 0;
|
||||
// add snapshot to animate
|
||||
transitionContext.containerView.addSubview(sharedElementSnapshot);
|
||||
}
|
||||
|
||||
const cleanupPresent = () => {
|
||||
for (const presented of owner.sharedElements.presented) {
|
||||
presented.view.opacity = presented.startOpacity;
|
||||
}
|
||||
for (const presenting of owner.sharedElements.presenting) {
|
||||
presenting.snapshot.removeFromSuperview();
|
||||
}
|
||||
SharedTransition.updateState(owner.id, {
|
||||
activeType: SharedTransitionAnimationType.dismiss,
|
||||
});
|
||||
owner.presenting.view.removeFromSuperview();
|
||||
transitionContext.completeTransition(true);
|
||||
};
|
||||
const updateFramePresent = () => {
|
||||
// https://stackoverflow.com/a/27997678/1418981
|
||||
// In order to have proper layout. Seems mostly needed when presenting.
|
||||
// For instance during presentation, destination view doesn't account navigation bar height.
|
||||
// Not sure if best to leave all the time?
|
||||
// owner.presented.view.setNeedsLayout();
|
||||
// owner.presented.view.layoutIfNeeded();
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('3. Animating shared elements:');
|
||||
}
|
||||
for (const presented of owner.sharedElements.presented) {
|
||||
const presentingMatch = owner.sharedElements.presenting.find((v) => v.view.sharedTransitionTag === presented.view.sharedTransitionTag);
|
||||
// Workaround wrong origin due ongoing layout process.
|
||||
const updatedEndFrame = presented.view.ios.convertRectToView(presented.view.ios.bounds, transitionContext.containerView);
|
||||
const correctedEndFrame = CGRectMake(updatedEndFrame.origin.x, updatedEndFrame.origin.y, presentingMatch.endFrame.size.width, presentingMatch.endFrame.size.height);
|
||||
presentingMatch.snapshot.frame = correctedEndFrame;
|
||||
|
||||
// apply view and layer properties to the snapshot view to match the source/presented view
|
||||
iOSNativeHelper.copyLayerProperties(presentingMatch.snapshot, presented.view.ios);
|
||||
// create a snapshot of the presented view
|
||||
presentingMatch.snapshot.image = iOSNativeHelper.snapshotView(presented.view.ios, Screen.mainScreen.scale);
|
||||
// apply correct alpha
|
||||
presentingMatch.snapshot.alpha = presentingMatch.endOpacity;
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(`---> ${presentingMatch.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(correctedEndFrame));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// starting page properties
|
||||
const toProps = state.toPageStart;
|
||||
owner.presented.view.alpha = isNumber(toProps?.opacity) ? toProps?.opacity : 0;
|
||||
|
||||
const startX = isNumber(toProps?.x) ? toProps?.x : Screen.mainScreen.widthDIPs;
|
||||
const startY = isNumber(toProps?.y) ? toProps?.y : 0;
|
||||
const startWidth = isNumber(toProps?.width) ? toProps?.width : Screen.mainScreen.widthDIPs;
|
||||
const startHeight = isNumber(toProps?.height) ? toProps?.height : Screen.mainScreen.heightDIPs;
|
||||
owner.presented.view.frame = CGRectMake(startX, startY, startWidth, startHeight);
|
||||
|
||||
const animateProperties = () => {
|
||||
const props = state.toPageEnd;
|
||||
// animate page properties to the following:
|
||||
owner.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 1;
|
||||
|
||||
const endX = isNumber(props?.x) ? props?.x : 0;
|
||||
const endY = isNumber(props?.y) ? props?.y : 0;
|
||||
const endWidth = isNumber(props?.width) ? props?.width : Screen.mainScreen.widthDIPs;
|
||||
const endHeight = isNumber(props?.height) ? props?.height : Screen.mainScreen.heightDIPs;
|
||||
owner.presented.view.frame = CGRectMake(endX, endY, endWidth, endHeight);
|
||||
};
|
||||
if (isNumber(toProps?.duration)) {
|
||||
// override spring and use only linear animation
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
toProps?.duration / 1000,
|
||||
() => {
|
||||
animateProperties();
|
||||
updateFramePresent();
|
||||
},
|
||||
() => {
|
||||
cleanupPresent();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const spring = toProps?.spring;
|
||||
iOSNativeHelper.animateWithSpring({
|
||||
tension: isNumber(spring?.tension) ? spring?.tension : DEFAULT_SPRING.tension,
|
||||
friction: isNumber(spring?.friction) ? spring?.tension : DEFAULT_SPRING.friction,
|
||||
animations: () => {
|
||||
animateProperties();
|
||||
|
||||
updateFramePresent();
|
||||
},
|
||||
completion: () => {
|
||||
cleanupPresent();
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UINavigationControllerOperation.Pop: {
|
||||
console.log('popping!');
|
||||
transitionContext.containerView.insertSubviewBelowSubview(owner.presenting.view, owner.presented.view);
|
||||
|
||||
// console.log('transitionContext.containerView.subviews.count:', transitionContext.containerView.subviews.count);
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(' PageTransition: Pop');
|
||||
console.log(
|
||||
`1. Dismiss sharedTransitionTags to animate:`,
|
||||
owner.sharedElements.presented.map((p) => p.view.sharedTransitionTag)
|
||||
);
|
||||
|
||||
console.log(`2. Add back previously stored sharedElements to dismiss:`);
|
||||
}
|
||||
|
||||
for (const p of owner.sharedElements.presented) {
|
||||
p.view.opacity = 0;
|
||||
}
|
||||
for (const p of owner.sharedElements.presenting) {
|
||||
p.snapshot.alpha = p.endOpacity;
|
||||
transitionContext.containerView.addSubview(p.snapshot);
|
||||
}
|
||||
|
||||
const cleanupDismiss = () => {
|
||||
for (const presenting of owner.sharedElements.presenting) {
|
||||
presenting.view.opacity = presenting.startOpacity;
|
||||
presenting.snapshot.removeFromSuperview();
|
||||
}
|
||||
SharedTransition.finishState(owner.id);
|
||||
transitionContext.completeTransition(true);
|
||||
};
|
||||
const updateFrameDismiss = () => {
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('3. Dismissing shared elements:');
|
||||
}
|
||||
for (const presenting of owner.sharedElements.presenting) {
|
||||
iOSNativeHelper.copyLayerProperties(presenting.snapshot, presenting.view.ios);
|
||||
presenting.snapshot.frame = presenting.startFrame;
|
||||
presenting.snapshot.alpha = presenting.startOpacity;
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(`---> ${presenting.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(presenting.startFrame));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const props = state.fromPageEnd;
|
||||
|
||||
const animateProperties = () => {
|
||||
owner.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 0;
|
||||
|
||||
const endX = isNumber(props?.x) ? props?.x : Screen.mainScreen.widthDIPs;
|
||||
const endY = isNumber(props?.y) ? props?.y : 0;
|
||||
const endWidth = isNumber(props?.width) ? props?.width : Screen.mainScreen.widthDIPs;
|
||||
const endHeight = isNumber(props?.height) ? props?.height : Screen.mainScreen.heightDIPs;
|
||||
owner.presented.view.frame = CGRectMake(endX, endY, endWidth, endHeight);
|
||||
};
|
||||
|
||||
if (isNumber(props?.duration)) {
|
||||
// override spring and use only linear animation
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
props?.duration / 1000,
|
||||
() => {
|
||||
animateProperties();
|
||||
updateFrameDismiss();
|
||||
},
|
||||
() => {
|
||||
cleanupDismiss();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const spring = props?.spring;
|
||||
iOSNativeHelper.animateWithSpring({
|
||||
tension: isNumber(spring?.tension) ? spring?.tension : DEFAULT_SPRING.tension,
|
||||
friction: isNumber(spring?.friction) ? spring?.tension : DEFAULT_SPRING.friction,
|
||||
animations: () => {
|
||||
animateProperties();
|
||||
|
||||
updateFrameDismiss();
|
||||
},
|
||||
completion: () => {
|
||||
cleanupDismiss();
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface SharedTransitionInteractiveOptions {
|
||||
* By default, we handle this via a formula like this for an interactive page back transition:
|
||||
* - return eventData.deltaX / (eventData.ios.view.bounds.size.width / 2);
|
||||
* @param eventData PanGestureEventData
|
||||
* @returns Should return a percentage value
|
||||
* @returns The percentage value to be used as the finish/cancel threshold
|
||||
*/
|
||||
percentFormula?: (eventData: PanGestureEventData) => number;
|
||||
}
|
||||
@@ -63,6 +63,9 @@ export interface SharedTransitionConfig {
|
||||
export interface SharedTransitionState extends SharedTransitionConfig {
|
||||
activeType?: SharedTransitionAnimationType;
|
||||
toPage?: ViewBase;
|
||||
// used internally for determining interactive gesture state of the transition
|
||||
interactiveBegan?: boolean;
|
||||
interactiveCancelled?: boolean;
|
||||
}
|
||||
type SharedTransitionPageProperties = {
|
||||
x?: number;
|
||||
|
||||
@@ -1,74 +1,112 @@
|
||||
import { Transition } from '.';
|
||||
import { Screen } from '../../platform';
|
||||
|
||||
const leftEdge = CGAffineTransformMakeTranslation(-Screen.mainScreen.widthDIPs, 0);
|
||||
const rightEdge = CGAffineTransformMakeTranslation(Screen.mainScreen.widthDIPs, 0);
|
||||
const topEdge = CGAffineTransformMakeTranslation(0, -Screen.mainScreen.heightDIPs);
|
||||
const bottomEdge = CGAffineTransformMakeTranslation(0, Screen.mainScreen.heightDIPs);
|
||||
import { DEFAULT_DURATION } from './shared-transition';
|
||||
|
||||
export class SlideTransition extends Transition {
|
||||
private _direction: string;
|
||||
transitionController: SlideTransitionController;
|
||||
presented: UIViewController;
|
||||
presenting: UIViewController;
|
||||
operation: number;
|
||||
direction: string;
|
||||
|
||||
constructor(direction: string, duration: number, curve: any) {
|
||||
super(duration, curve);
|
||||
this._direction = direction;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public animateIOSTransition(transitionContext: UIViewControllerContextTransitioning, fromViewCtrl: UIViewController, toViewCtrl: UIViewController, operation: UINavigationControllerOperation): void {
|
||||
const toView = toViewCtrl.view;
|
||||
const originalToViewTransform = toView.transform;
|
||||
const fromView = fromViewCtrl.view;
|
||||
const originalFromViewTransform = fromView.transform;
|
||||
|
||||
let fromViewEndTransform: CGAffineTransform;
|
||||
let toViewBeginTransform: CGAffineTransform;
|
||||
const push = operation === UINavigationControllerOperation.Push;
|
||||
|
||||
switch (this._direction) {
|
||||
case 'left':
|
||||
toViewBeginTransform = push ? rightEdge : leftEdge;
|
||||
fromViewEndTransform = push ? leftEdge : rightEdge;
|
||||
break;
|
||||
case 'right':
|
||||
toViewBeginTransform = push ? leftEdge : rightEdge;
|
||||
fromViewEndTransform = push ? rightEdge : leftEdge;
|
||||
break;
|
||||
case 'top':
|
||||
toViewBeginTransform = push ? bottomEdge : topEdge;
|
||||
fromViewEndTransform = push ? topEdge : bottomEdge;
|
||||
break;
|
||||
case 'bottom':
|
||||
toViewBeginTransform = push ? topEdge : bottomEdge;
|
||||
fromViewEndTransform = push ? bottomEdge : topEdge;
|
||||
break;
|
||||
}
|
||||
|
||||
toView.transform = toViewBeginTransform;
|
||||
fromView.transform = CGAffineTransformIdentity;
|
||||
|
||||
switch (operation) {
|
||||
case UINavigationControllerOperation.Push:
|
||||
transitionContext.containerView.insertSubviewAboveSubview(toView, fromView);
|
||||
break;
|
||||
case UINavigationControllerOperation.Pop:
|
||||
transitionContext.containerView.insertSubviewBelowSubview(toView, fromView);
|
||||
break;
|
||||
}
|
||||
|
||||
const duration = this.getDuration();
|
||||
const curve = this.getCurve();
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
duration,
|
||||
() => {
|
||||
UIView.setAnimationCurve(curve);
|
||||
toView.transform = CGAffineTransformIdentity;
|
||||
fromView.transform = fromViewEndTransform;
|
||||
},
|
||||
(finished: boolean) => {
|
||||
toView.transform = originalToViewTransform;
|
||||
fromView.transform = originalFromViewTransform;
|
||||
transitionContext.completeTransition(finished);
|
||||
}
|
||||
);
|
||||
iosNavigatedController(navigationController: UINavigationController, operation: number, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
|
||||
this.transitionController = SlideTransitionController.initWithOwner(new WeakRef(this));
|
||||
this.presented = toVC;
|
||||
this.presenting = fromVC;
|
||||
this.operation = operation;
|
||||
// console.log('presenting:', presenting)
|
||||
return this.transitionController;
|
||||
}
|
||||
}
|
||||
|
||||
@NativeClass()
|
||||
export class SlideTransitionController extends NSObject implements UIViewControllerAnimatedTransitioning {
|
||||
static ObjCProtocols = [UIViewControllerAnimatedTransitioning];
|
||||
owner: WeakRef<SlideTransition>;
|
||||
|
||||
static initWithOwner(owner: WeakRef<SlideTransition>) {
|
||||
const ctrl = <SlideTransitionController>SlideTransitionController.new();
|
||||
ctrl.owner = owner;
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
return owner.getDuration();
|
||||
}
|
||||
return DEFAULT_DURATION;
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
console.log('SlideTransitionController animateTransition');
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
const toView = owner.presented.view;
|
||||
const originalToViewTransform = toView.transform;
|
||||
const fromView = owner.presenting.view;
|
||||
const originalFromViewTransform = fromView.transform;
|
||||
|
||||
let fromViewEndTransform: CGAffineTransform;
|
||||
let toViewBeginTransform: CGAffineTransform;
|
||||
const push = owner.operation === UINavigationControllerOperation.Push;
|
||||
|
||||
const leftEdge = CGAffineTransformMakeTranslation(-Screen.mainScreen.widthDIPs, 0);
|
||||
const rightEdge = CGAffineTransformMakeTranslation(Screen.mainScreen.widthDIPs, 0);
|
||||
const topEdge = CGAffineTransformMakeTranslation(0, -Screen.mainScreen.heightDIPs);
|
||||
const bottomEdge = CGAffineTransformMakeTranslation(0, Screen.mainScreen.heightDIPs);
|
||||
|
||||
switch (owner.direction) {
|
||||
case 'left':
|
||||
toViewBeginTransform = push ? rightEdge : leftEdge;
|
||||
fromViewEndTransform = push ? leftEdge : rightEdge;
|
||||
break;
|
||||
case 'right':
|
||||
toViewBeginTransform = push ? leftEdge : rightEdge;
|
||||
fromViewEndTransform = push ? rightEdge : leftEdge;
|
||||
break;
|
||||
case 'top':
|
||||
toViewBeginTransform = push ? bottomEdge : topEdge;
|
||||
fromViewEndTransform = push ? topEdge : bottomEdge;
|
||||
break;
|
||||
case 'bottom':
|
||||
toViewBeginTransform = push ? topEdge : bottomEdge;
|
||||
fromViewEndTransform = push ? bottomEdge : topEdge;
|
||||
break;
|
||||
}
|
||||
|
||||
toView.transform = toViewBeginTransform;
|
||||
fromView.transform = CGAffineTransformIdentity;
|
||||
|
||||
switch (owner.operation) {
|
||||
case UINavigationControllerOperation.Push:
|
||||
transitionContext.containerView.insertSubviewAboveSubview(toView, fromView);
|
||||
break;
|
||||
case UINavigationControllerOperation.Pop:
|
||||
transitionContext.containerView.insertSubviewBelowSubview(toView, fromView);
|
||||
break;
|
||||
}
|
||||
|
||||
const duration = owner.getDuration();
|
||||
const curve = owner.getCurve();
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
duration,
|
||||
() => {
|
||||
UIView.setAnimationCurve(curve);
|
||||
toView.transform = CGAffineTransformIdentity;
|
||||
fromView.transform = fromViewEndTransform;
|
||||
},
|
||||
(finished: boolean) => {
|
||||
toView.transform = originalToViewTransform;
|
||||
fromView.transform = originalFromViewTransform;
|
||||
transitionContext.completeTransition(finished);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user