mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-11-04 12:58:38 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			637 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			637 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import type { TransitionInteractiveState, TransitionNavigationType } from '.';
 | 
						|
import { getPageStartDefaultsForType, getRectFromProps, getSpringFromProps, SharedTransition, SharedTransitionAnimationType, SharedTransitionState } from './shared-transition';
 | 
						|
import { isNumber } from '../../utils/types';
 | 
						|
import { Screen } from '../../platform';
 | 
						|
import { CORE_ANIMATION_DEFAULTS } from '../../utils/animation-helpers';
 | 
						|
import { ios as iOSUtils } from '../../utils/native-helper';
 | 
						|
 | 
						|
interface PlatformTransitionInteractiveState extends TransitionInteractiveState {
 | 
						|
	transitionContext?: UIViewControllerContextTransitioning;
 | 
						|
	propertyAnimator?: UIViewPropertyAnimator;
 | 
						|
}
 | 
						|
 | 
						|
export class SharedTransitionHelper {
 | 
						|
	static animate(state: SharedTransitionState, transitionContext: UIViewControllerContextTransitioning, type: TransitionNavigationType) {
 | 
						|
		const transition = state.instance;
 | 
						|
		setTimeout(async () => {
 | 
						|
			// Run on next tick
 | 
						|
			// ensures that existing UI state finishes before snapshotting
 | 
						|
			// (eg, button touch up state)
 | 
						|
			switch (state.activeType) {
 | 
						|
				case SharedTransitionAnimationType.present: {
 | 
						|
					// console.log('-- Transition present --');
 | 
						|
					SharedTransition.notifyEvent(SharedTransition.startedEvent, {
 | 
						|
						id: transition.id,
 | 
						|
						type,
 | 
						|
						action: 'present',
 | 
						|
					});
 | 
						|
 | 
						|
					if (type === 'modal') {
 | 
						|
						transitionContext.containerView.addSubview(transition.presented.view);
 | 
						|
					} else if (type === 'page') {
 | 
						|
						transitionContext.containerView.insertSubviewAboveSubview(transition.presented.view, transition.presenting.view);
 | 
						|
					}
 | 
						|
					transition.presented.view.layoutIfNeeded();
 | 
						|
 | 
						|
					const { sharedElements, presented, presenting } = SharedTransition.getSharedElements(state.page, state.toPage);
 | 
						|
					const sharedElementTags = sharedElements.map((v) => v.sharedTransitionTag);
 | 
						|
					if (!transition.sharedElements) {
 | 
						|
						transition.sharedElements = {
 | 
						|
							presented: [],
 | 
						|
							presenting: [],
 | 
						|
							independent: [],
 | 
						|
						};
 | 
						|
					}
 | 
						|
 | 
						|
					if (SharedTransition.DEBUG) {
 | 
						|
						console.log(`  ${type}: Present`);
 | 
						|
						console.log(`1. Found sharedTransitionTags to animate:`, sharedElementTags);
 | 
						|
 | 
						|
						console.log(`2. Take snapshots of shared elements and position them based on presenting view:`);
 | 
						|
					}
 | 
						|
 | 
						|
					const pageOut = state.pageOut;
 | 
						|
					const pageStart = state.pageStart;
 | 
						|
 | 
						|
					const startFrame = getRectFromProps(pageStart, getPageStartDefaultsForType(type));
 | 
						|
 | 
						|
					const pageEnd = state.pageEnd;
 | 
						|
					const pageEndTags = pageEnd?.sharedTransitionTags || {};
 | 
						|
					// console.log('pageEndIndependentTags:', pageEndIndependentTags);
 | 
						|
 | 
						|
					const positionSharedTags = async () => {
 | 
						|
						for (const presentingView of sharedElements) {
 | 
						|
							const presentingSharedElement = presentingView.ios;
 | 
						|
							// console.log('fromTarget instanceof UIImageView:', fromTarget instanceof UIImageView)
 | 
						|
 | 
						|
							// 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 pageEndProps = pageEndTags[presentingView.sharedTransitionTag];
 | 
						|
 | 
						|
							const snapshot = UIImageView.alloc().init();
 | 
						|
							if (pageEndProps?.callback) {
 | 
						|
								await pageEndProps?.callback(presentedView, 'present');
 | 
						|
							}
 | 
						|
 | 
						|
							// 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', () => {
 | 
						|
									snapshot.image = iOSUtils.snapshotView(presentedSharedElement, Screen.mainScreen.scale);
 | 
						|
									snapshot.tintColor = presentedSharedElement.tintColor;
 | 
						|
								});
 | 
						|
 | 
						|
								snapshot.tintColor = presentedSharedElement.tintColor;
 | 
						|
								snapshot.contentMode = presentedSharedElement.contentMode;
 | 
						|
							}
 | 
						|
 | 
						|
							iOSUtils.copyLayerProperties(snapshot, presentingSharedElement, pageEndProps?.propertiesToMatch as any);
 | 
						|
							snapshot.clipsToBounds = true;
 | 
						|
							// console.log('---> snapshot: ', snapshot);
 | 
						|
 | 
						|
							const startFrame = presentingSharedElement.convertRectToView(presentingSharedElement.bounds, transitionContext.containerView);
 | 
						|
							const endFrame = presentedSharedElement.convertRectToView(presentedSharedElement.bounds, transitionContext.containerView);
 | 
						|
							snapshot.frame = startFrame;
 | 
						|
							if (SharedTransition.DEBUG) {
 | 
						|
								console.log('---> ', presentingView.sharedTransitionTag, ' frame:', iOSUtils.printCGRect(snapshot.frame));
 | 
						|
							}
 | 
						|
 | 
						|
							transition.sharedElements.presenting.push({
 | 
						|
								view: presentingView,
 | 
						|
								startFrame,
 | 
						|
								endFrame,
 | 
						|
								snapshot,
 | 
						|
								startOpacity: presentingView.opacity,
 | 
						|
								endOpacity: isNumber(pageEndProps?.opacity) ? pageEndProps.opacity : presentedView.opacity,
 | 
						|
								propertiesToMatch: pageEndProps?.propertiesToMatch,
 | 
						|
								zIndex: isNumber(pageEndProps?.zIndex) ? pageEndProps.zIndex : 0,
 | 
						|
							});
 | 
						|
							transition.sharedElements.presented.push({
 | 
						|
								view: presentedView,
 | 
						|
								startFrame: endFrame,
 | 
						|
								endFrame: startFrame,
 | 
						|
								startOpacity: presentedView.opacity,
 | 
						|
								endOpacity: presentingView.opacity,
 | 
						|
								propertiesToMatch: pageEndProps?.propertiesToMatch,
 | 
						|
							});
 | 
						|
 | 
						|
							// set initial opacity to match the source view opacity
 | 
						|
							snapshot.alpha = presentingView.opacity;
 | 
						|
							// hide both while animating within the transition context
 | 
						|
							presentingView.opacity = 0;
 | 
						|
							presentedView.opacity = 0;
 | 
						|
						}
 | 
						|
					};
 | 
						|
 | 
						|
					const positionIndependentTags = async () => {
 | 
						|
						// independent tags
 | 
						|
						for (const tag in pageEndTags) {
 | 
						|
							// only handle if independent (otherwise it's shared between both pages and handled above)
 | 
						|
							if (!sharedElementTags.includes(tag)) {
 | 
						|
								// only consider start when there's a matching end
 | 
						|
								const pageStartIndependentProps = pageStart?.sharedTransitionTags ? pageStart?.sharedTransitionTags[tag] : null;
 | 
						|
								// console.log('start:', tag, pageStartIndependentProps);
 | 
						|
								const pageEndProps = pageEndTags[tag];
 | 
						|
								let independentView = presenting.find((v) => v.sharedTransitionTag === tag);
 | 
						|
								let isPresented = false;
 | 
						|
								if (!independentView) {
 | 
						|
									independentView = presented.find((v) => v.sharedTransitionTag === tag);
 | 
						|
									if (!independentView) {
 | 
						|
										break;
 | 
						|
									}
 | 
						|
									isPresented = true;
 | 
						|
								}
 | 
						|
								const independentSharedElement: UIView = independentView.ios;
 | 
						|
 | 
						|
								if (pageEndProps?.callback) {
 | 
						|
									await pageEndProps?.callback(independentView, 'present');
 | 
						|
								}
 | 
						|
 | 
						|
								// let snapshot: UIImageView;
 | 
						|
								// if (isPresented) {
 | 
						|
								// 	snapshot = UIImageView.alloc().init();
 | 
						|
								// } else {
 | 
						|
								const snapshot = UIImageView.alloc().initWithImage(iOSUtils.snapshotView(independentSharedElement, Screen.mainScreen.scale));
 | 
						|
								// }
 | 
						|
 | 
						|
								if (independentSharedElement instanceof UIImageView) {
 | 
						|
									// in case the image is loaded async, we need to update the snapshot when it changes
 | 
						|
									// todo: remove listener on transition end
 | 
						|
									// if (isPresented) {
 | 
						|
									// 	independentView.on('imageSourceChange', () => {
 | 
						|
									// 		snapshot.image = iOSNativeHelper.snapshotView(independentSharedElement, Screen.mainScreen.scale);
 | 
						|
									// 		snapshot.tintColor = independentSharedElement.tintColor;
 | 
						|
									// 	});
 | 
						|
									// }
 | 
						|
 | 
						|
									snapshot.tintColor = independentSharedElement.tintColor;
 | 
						|
									snapshot.contentMode = independentSharedElement.contentMode;
 | 
						|
								}
 | 
						|
								snapshot.clipsToBounds = true;
 | 
						|
 | 
						|
								const startFrame = independentSharedElement.convertRectToView(independentSharedElement.bounds, transitionContext.containerView);
 | 
						|
								const startFrameRect = getRectFromProps(pageStartIndependentProps);
 | 
						|
								// adjust for any specified start positions
 | 
						|
								const startFrameAdjusted = CGRectMake(startFrame.origin.x + startFrameRect.x, startFrame.origin.y + startFrameRect.y, startFrame.size.width, startFrame.size.height);
 | 
						|
								// console.log('startFrameAdjusted:', tag, iOSNativeHelper.printCGRect(startFrameAdjusted));
 | 
						|
								// if (pageStartIndependentProps?.scale) {
 | 
						|
								// 	snapshot.transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(startFrameAdjusted.origin.x, startFrameAdjusted.origin.y), CGAffineTransformMakeScale(pageStartIndependentProps.scale.x, pageStartIndependentProps.scale.y))
 | 
						|
								// } else {
 | 
						|
								snapshot.frame = startFrame; //startFrameAdjusted;
 | 
						|
								// }
 | 
						|
								if (SharedTransition.DEBUG) {
 | 
						|
									console.log('---> ', independentView.sharedTransitionTag, ' frame:', iOSUtils.printCGRect(snapshot.frame));
 | 
						|
								}
 | 
						|
 | 
						|
								const endFrameRect = getRectFromProps(pageEndProps);
 | 
						|
 | 
						|
								const endFrame = CGRectMake(startFrame.origin.x + endFrameRect.x, startFrame.origin.y + endFrameRect.y, startFrame.size.width, startFrame.size.height);
 | 
						|
								// console.log('endFrame:', tag, iOSNativeHelper.printCGRect(endFrame));
 | 
						|
								transition.sharedElements.independent.push({
 | 
						|
									view: independentView,
 | 
						|
									isPresented,
 | 
						|
									startFrame,
 | 
						|
									snapshot,
 | 
						|
									endFrame,
 | 
						|
									startTransform: independentSharedElement.transform,
 | 
						|
									scale: pageEndProps.scale,
 | 
						|
									startOpacity: independentView.opacity,
 | 
						|
									endOpacity: isNumber(pageEndProps.opacity) ? pageEndProps.opacity : 0,
 | 
						|
									propertiesToMatch: pageEndProps?.propertiesToMatch,
 | 
						|
									zIndex: isNumber(pageEndProps?.zIndex) ? pageEndProps.zIndex : 0,
 | 
						|
								});
 | 
						|
 | 
						|
								independentView.opacity = 0;
 | 
						|
							}
 | 
						|
						}
 | 
						|
					};
 | 
						|
 | 
						|
					// position all sharedTransitionTag elements
 | 
						|
					await positionSharedTags();
 | 
						|
					await positionIndependentTags();
 | 
						|
					// combine to order by zIndex and add to transition context
 | 
						|
					const snapshotData = transition.sharedElements.presenting.concat(transition.sharedElements.independent);
 | 
						|
					snapshotData.sort((a, b) => (a.zIndex > b.zIndex ? 1 : -1));
 | 
						|
					if (SharedTransition.DEBUG) {
 | 
						|
						console.log(
 | 
						|
							`zIndex settings:`,
 | 
						|
							snapshotData.map((s) => {
 | 
						|
								return {
 | 
						|
									sharedTransitionTag: s.view.sharedTransitionTag,
 | 
						|
									zIndex: s.zIndex,
 | 
						|
								};
 | 
						|
							}),
 | 
						|
						);
 | 
						|
					}
 | 
						|
 | 
						|
					for (const data of snapshotData) {
 | 
						|
						// add snapshot to animate
 | 
						|
						transitionContext.containerView.addSubview(data.snapshot);
 | 
						|
					}
 | 
						|
 | 
						|
					// Important: always set after above shared element positions have had their start positions set
 | 
						|
					transition.presented.view.alpha = isNumber(pageStart?.opacity) ? pageStart?.opacity : 0;
 | 
						|
					transition.presented.view.frame = CGRectMake(startFrame.x, startFrame.y, startFrame.width, startFrame.height);
 | 
						|
 | 
						|
					const cleanupPresent = () => {
 | 
						|
						for (const presented of transition.sharedElements.presented) {
 | 
						|
							presented.view.opacity = presented.startOpacity;
 | 
						|
						}
 | 
						|
						for (const presenting of transition.sharedElements.presenting) {
 | 
						|
							presenting.snapshot.removeFromSuperview();
 | 
						|
						}
 | 
						|
						for (const independent of transition.sharedElements.independent) {
 | 
						|
							independent.snapshot.removeFromSuperview();
 | 
						|
							if (independent.isPresented) {
 | 
						|
								independent.view.opacity = independent.startOpacity;
 | 
						|
							}
 | 
						|
						}
 | 
						|
						SharedTransition.updateState(transition.id, {
 | 
						|
							activeType: SharedTransitionAnimationType.dismiss,
 | 
						|
						});
 | 
						|
						if (type === 'page') {
 | 
						|
							transition.presenting.view.removeFromSuperview();
 | 
						|
						}
 | 
						|
						transitionContext.completeTransition(true);
 | 
						|
						SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
 | 
						|
							id: transition.id,
 | 
						|
							type,
 | 
						|
							action: 'present',
 | 
						|
						});
 | 
						|
					};
 | 
						|
 | 
						|
					const animateProperties = () => {
 | 
						|
						if (SharedTransition.DEBUG) {
 | 
						|
							console.log('3. Animating shared elements:');
 | 
						|
						}
 | 
						|
						transition.presented.view.alpha = isNumber(pageEnd?.opacity) ? pageEnd?.opacity : 1;
 | 
						|
 | 
						|
						const endFrame = getRectFromProps(pageEnd);
 | 
						|
						transition.presented.view.frame = CGRectMake(endFrame.x, endFrame.y, endFrame.width, endFrame.height);
 | 
						|
 | 
						|
						if (pageOut) {
 | 
						|
							if (isNumber(pageOut.opacity)) {
 | 
						|
								transition.presenting.view.alpha = pageOut?.opacity;
 | 
						|
							}
 | 
						|
 | 
						|
							const outFrame = getRectFromProps(pageOut);
 | 
						|
							transition.presenting.view.frame = CGRectMake(outFrame.x, outFrame.y, outFrame.width, outFrame.height);
 | 
						|
						}
 | 
						|
						// animate page properties to the following:
 | 
						|
						// 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();
 | 
						|
 | 
						|
						for (const presented of transition.sharedElements.presented) {
 | 
						|
							const presentingMatch = transition.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
 | 
						|
							iOSUtils.copyLayerProperties(presentingMatch.snapshot, presented.view.ios, presented.propertiesToMatch as any);
 | 
						|
							// create a snapshot of the presented view
 | 
						|
							presentingMatch.snapshot.image = iOSUtils.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: `, iOSUtils.printCGRect(correctedEndFrame));
 | 
						|
							}
 | 
						|
						}
 | 
						|
						for (const independent of transition.sharedElements.independent) {
 | 
						|
							const endFrame: CGRect = independent.endFrame;
 | 
						|
							// if (independent.isPresented) {
 | 
						|
							// 	const updatedEndFrame = independent.view.ios.convertRectToView(independent.view.ios.bounds, transitionContext.containerView);
 | 
						|
							// 	endFrame = CGRectMake(updatedEndFrame.origin.x, updatedEndFrame.origin.y, independent.endFrame.size.width, independent.endFrame.size.height);
 | 
						|
							// }
 | 
						|
							if (independent.scale) {
 | 
						|
								independent.snapshot.transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(endFrame.origin.x, endFrame.origin.y), CGAffineTransformMakeScale(independent.scale.x, independent.scale.y));
 | 
						|
							} else {
 | 
						|
								independent.snapshot.frame = endFrame;
 | 
						|
							}
 | 
						|
							independent.snapshot.alpha = independent.endOpacity;
 | 
						|
 | 
						|
							if (SharedTransition.DEBUG) {
 | 
						|
								console.log(`---> ${independent.view.sharedTransitionTag} animate to: `, iOSUtils.printCGRect(independent.endFrame));
 | 
						|
							}
 | 
						|
						}
 | 
						|
					};
 | 
						|
 | 
						|
					if (isNumber(pageEnd?.duration)) {
 | 
						|
						// override spring and use only linear animation
 | 
						|
						UIView.animateWithDurationDelayOptionsAnimationsCompletion(
 | 
						|
							pageEnd?.duration / 1000,
 | 
						|
							0,
 | 
						|
							UIViewAnimationOptions.CurveEaseInOut,
 | 
						|
							() => {
 | 
						|
								animateProperties();
 | 
						|
							},
 | 
						|
							() => {
 | 
						|
								cleanupPresent();
 | 
						|
							},
 | 
						|
						);
 | 
						|
					} else {
 | 
						|
						iOSUtils.animateWithSpring({
 | 
						|
							...getSpringFromProps(pageEnd?.spring),
 | 
						|
							animations: () => {
 | 
						|
								animateProperties();
 | 
						|
							},
 | 
						|
							completion: () => {
 | 
						|
								cleanupPresent();
 | 
						|
							},
 | 
						|
						});
 | 
						|
					}
 | 
						|
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				case SharedTransitionAnimationType.dismiss: {
 | 
						|
					// console.log('-- Transition dismiss --');
 | 
						|
					SharedTransition.notifyEvent(SharedTransition.startedEvent, {
 | 
						|
						id: transition.id,
 | 
						|
						type,
 | 
						|
						action: 'dismiss',
 | 
						|
					});
 | 
						|
					if (type === 'page') {
 | 
						|
						transitionContext.containerView.insertSubviewBelowSubview(transition.presenting.view, transition.presented.view);
 | 
						|
					}
 | 
						|
 | 
						|
					// console.log('transitionContext.containerView.subviews.count:', transitionContext.containerView.subviews.count);
 | 
						|
 | 
						|
					if (SharedTransition.DEBUG) {
 | 
						|
						console.log(`  ${type}: Dismiss`);
 | 
						|
						console.log(
 | 
						|
							`1. Dismiss sharedTransitionTags to animate:`,
 | 
						|
							transition.sharedElements.presented.map((p) => p.view.sharedTransitionTag),
 | 
						|
						);
 | 
						|
 | 
						|
						console.log(`2. Add back previously stored sharedElements to dismiss:`);
 | 
						|
					}
 | 
						|
 | 
						|
					const pageOut = state.pageOut;
 | 
						|
					const pageEnd = state.pageEnd;
 | 
						|
					const pageEndTags = pageEnd?.sharedTransitionTags || {};
 | 
						|
					const pageReturn = state.pageReturn;
 | 
						|
 | 
						|
					for (const p of transition.sharedElements.presented) {
 | 
						|
						p.view.opacity = 0;
 | 
						|
					}
 | 
						|
 | 
						|
					// combine to order by zIndex and add to transition context
 | 
						|
					const snapshotData = transition.sharedElements.presenting.concat(transition.sharedElements.independent);
 | 
						|
					snapshotData.sort((a, b) => (a.zIndex > b.zIndex ? 1 : -1));
 | 
						|
					if (SharedTransition.DEBUG) {
 | 
						|
						console.log(
 | 
						|
							`zIndex settings:`,
 | 
						|
							snapshotData.map((s) => {
 | 
						|
								return {
 | 
						|
									sharedTransitionTag: s.view.sharedTransitionTag,
 | 
						|
									zIndex: s.zIndex,
 | 
						|
								};
 | 
						|
							}),
 | 
						|
						);
 | 
						|
					}
 | 
						|
 | 
						|
					// first loop through all the shared elements and fire the callback
 | 
						|
					for (const data of snapshotData) {
 | 
						|
						const pageEndProps = pageEndTags[data.view.sharedTransitionTag];
 | 
						|
						if (pageEndProps?.callback) {
 | 
						|
							await pageEndProps?.callback(data.view, 'dismiss');
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					// now that all the callbacks had their chance to run, we can take the snapshots
 | 
						|
					for (const data of snapshotData) {
 | 
						|
						const view = data.view.ios;
 | 
						|
						const currentAlpha = view.alpha;
 | 
						|
						if (pageReturn?.useStartOpacity) {
 | 
						|
							// when desired, reset the alpha to the start value so the view is visible in the snapshot
 | 
						|
							view.alpha = data.startOpacity;
 | 
						|
						}
 | 
						|
 | 
						|
						// take a new snapshot
 | 
						|
						data.snapshot.image = iOSUtils.snapshotView(view, Screen.mainScreen.scale);
 | 
						|
 | 
						|
						// find the currently visible view with the same sharedTransitionTag
 | 
						|
						const fromView = transition.sharedElements.presented.find((p) => p.view.sharedTransitionTag === data.view.sharedTransitionTag)?.view;
 | 
						|
						if (fromView) {
 | 
						|
							// match the snapshot frame to the current frame of the fromView
 | 
						|
							data.snapshot.frame = fromView.ios.convertRectToView(fromView.ios.bounds, transitionContext.containerView);
 | 
						|
						}
 | 
						|
 | 
						|
						// snapshot has been taken, we can restore the alpha
 | 
						|
						view.alpha = currentAlpha;
 | 
						|
 | 
						|
						// we recalculate the startFrame because the view might have changed its position in the background
 | 
						|
						data.startFrame = view.convertRectToView(view.bounds, transitionContext.containerView);
 | 
						|
 | 
						|
						// add snapshot to animate
 | 
						|
						transitionContext.containerView.addSubview(data.snapshot);
 | 
						|
					}
 | 
						|
 | 
						|
					const cleanupDismiss = () => {
 | 
						|
						for (const presenting of transition.sharedElements.presenting) {
 | 
						|
							presenting.view.opacity = presenting.startOpacity;
 | 
						|
							presenting.snapshot.removeFromSuperview();
 | 
						|
						}
 | 
						|
						for (const independent of transition.sharedElements.independent) {
 | 
						|
							independent.view.opacity = independent.startOpacity;
 | 
						|
							independent.snapshot.removeFromSuperview();
 | 
						|
						}
 | 
						|
						SharedTransition.finishState(transition.id);
 | 
						|
						transition.sharedElements = null;
 | 
						|
						transitionContext.completeTransition(true);
 | 
						|
						SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
 | 
						|
							id: transition.id,
 | 
						|
							type,
 | 
						|
							action: 'dismiss',
 | 
						|
						});
 | 
						|
					};
 | 
						|
 | 
						|
					const animateProperties = () => {
 | 
						|
						if (SharedTransition.DEBUG) {
 | 
						|
							console.log('3. Dismissing shared elements:');
 | 
						|
						}
 | 
						|
 | 
						|
						transition.presented.view.alpha = isNumber(pageReturn?.opacity) ? pageReturn?.opacity : 0;
 | 
						|
 | 
						|
						const endFrame = getRectFromProps(pageReturn, getPageStartDefaultsForType(type));
 | 
						|
						transition.presented.view.frame = CGRectMake(endFrame.x, endFrame.y, endFrame.width, endFrame.height);
 | 
						|
 | 
						|
						if (pageOut) {
 | 
						|
							// always return to defaults if pageOut had been used
 | 
						|
							transition.presenting.view.alpha = 1;
 | 
						|
 | 
						|
							const outFrame = getRectFromProps(null);
 | 
						|
							transition.presenting.view.frame = CGRectMake(0, 0, outFrame.width, outFrame.height);
 | 
						|
						}
 | 
						|
 | 
						|
						for (const presenting of transition.sharedElements.presenting) {
 | 
						|
							iOSUtils.copyLayerProperties(presenting.snapshot, presenting.view.ios, presenting.propertiesToMatch as any);
 | 
						|
							presenting.snapshot.frame = presenting.startFrame;
 | 
						|
							presenting.snapshot.alpha = presenting.startOpacity;
 | 
						|
 | 
						|
							if (SharedTransition.DEBUG) {
 | 
						|
								console.log(`---> ${presenting.view.sharedTransitionTag} animate to: `, iOSUtils.printCGRect(presenting.snapshot.frame));
 | 
						|
							}
 | 
						|
						}
 | 
						|
 | 
						|
						for (const independent of transition.sharedElements.independent) {
 | 
						|
							independent.snapshot.alpha = independent.startOpacity;
 | 
						|
							if (independent.scale) {
 | 
						|
								independent.snapshot.transform = independent.startTransform;
 | 
						|
							} else {
 | 
						|
								independent.snapshot.frame = independent.startFrame;
 | 
						|
							}
 | 
						|
 | 
						|
							if (SharedTransition.DEBUG) {
 | 
						|
								console.log(`---> ${independent.view.sharedTransitionTag} animate to: `, iOSUtils.printCGRect(independent.snapshot.frame));
 | 
						|
							}
 | 
						|
						}
 | 
						|
					};
 | 
						|
 | 
						|
					if (isNumber(pageReturn?.duration)) {
 | 
						|
						// override spring and use only linear animation
 | 
						|
						UIView.animateWithDurationDelayOptionsAnimationsCompletion(
 | 
						|
							pageReturn?.duration / 1000,
 | 
						|
							0,
 | 
						|
							UIViewAnimationOptions.CurveEaseInOut,
 | 
						|
							() => {
 | 
						|
								animateProperties();
 | 
						|
							},
 | 
						|
							() => {
 | 
						|
								cleanupDismiss();
 | 
						|
							},
 | 
						|
						);
 | 
						|
					} else {
 | 
						|
						iOSUtils.animateWithSpring({
 | 
						|
							...getSpringFromProps(pageReturn?.spring),
 | 
						|
							animations: () => {
 | 
						|
								animateProperties();
 | 
						|
							},
 | 
						|
							completion: () => {
 | 
						|
								cleanupDismiss();
 | 
						|
							},
 | 
						|
						});
 | 
						|
					}
 | 
						|
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		});
 | 
						|
	}
 | 
						|
 | 
						|
	static interactiveStart(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType) {
 | 
						|
		SharedTransition.notifyEvent(SharedTransition.startedEvent, {
 | 
						|
			id: state.instance.id,
 | 
						|
			type,
 | 
						|
			action: 'interactiveStart',
 | 
						|
		});
 | 
						|
		switch (type) {
 | 
						|
			case 'page':
 | 
						|
				interactiveState.transitionContext.containerView.insertSubviewBelowSubview(state.instance.presenting.view, state.instance.presented.view);
 | 
						|
				break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	static interactiveUpdate(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType, percent: number) {
 | 
						|
		if (interactiveState) {
 | 
						|
			if (!interactiveState.added) {
 | 
						|
				interactiveState.added = true;
 | 
						|
				for (const p of state.instance.sharedElements.presented) {
 | 
						|
					p.view.opacity = 0;
 | 
						|
				}
 | 
						|
				for (const p of state.instance.sharedElements.presenting) {
 | 
						|
					p.snapshot.alpha = p.endOpacity;
 | 
						|
					interactiveState.transitionContext.containerView.addSubview(p.snapshot);
 | 
						|
				}
 | 
						|
 | 
						|
				const pageStart = state.pageStart;
 | 
						|
 | 
						|
				const startFrame = getRectFromProps(pageStart, getPageStartDefaultsForType(type));
 | 
						|
				interactiveState.propertyAnimator = UIViewPropertyAnimator.alloc().initWithDurationDampingRatioAnimations(1, 1, () => {
 | 
						|
					for (const p of state.instance.sharedElements.presenting) {
 | 
						|
						p.snapshot.frame = p.startFrame;
 | 
						|
						iOSUtils.copyLayerProperties(p.snapshot, p.view.ios, p.propertiesToMatch as any);
 | 
						|
 | 
						|
						p.snapshot.alpha = 1;
 | 
						|
					}
 | 
						|
					state.instance.presented.view.alpha = isNumber(state.pageReturn?.opacity) ? state.pageReturn?.opacity : 0;
 | 
						|
					state.instance.presented.view.frame = CGRectMake(startFrame.x, startFrame.y, state.instance.presented.view.bounds.size.width, state.instance.presented.view.bounds.size.height);
 | 
						|
				});
 | 
						|
			}
 | 
						|
 | 
						|
			interactiveState.propertyAnimator.fractionComplete = percent;
 | 
						|
			SharedTransition.notifyEvent(SharedTransition.interactiveUpdateEvent, {
 | 
						|
				id: state?.instance?.id,
 | 
						|
				type,
 | 
						|
				percent,
 | 
						|
			});
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	static interactiveCancel(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType) {
 | 
						|
		if (state?.instance && interactiveState?.added && interactiveState?.propertyAnimator) {
 | 
						|
			interactiveState.propertyAnimator.reversed = true;
 | 
						|
			const duration = isNumber(state.pageEnd?.duration) ? state.pageEnd?.duration / 1000 : CORE_ANIMATION_DEFAULTS.duration;
 | 
						|
			interactiveState.propertyAnimator.continueAnimationWithTimingParametersDurationFactor(null, duration);
 | 
						|
			setTimeout(() => {
 | 
						|
				for (const p of state.instance.sharedElements.presented) {
 | 
						|
					p.view.opacity = 1;
 | 
						|
				}
 | 
						|
				for (const p of state.instance.sharedElements.presenting) {
 | 
						|
					p.snapshot.removeFromSuperview();
 | 
						|
				}
 | 
						|
				state.instance.presented.view.alpha = 1;
 | 
						|
				interactiveState.propertyAnimator = null;
 | 
						|
				interactiveState.added = false;
 | 
						|
				interactiveState.transitionContext.cancelInteractiveTransition();
 | 
						|
				interactiveState.transitionContext.completeTransition(false);
 | 
						|
				SharedTransition.updateState(state?.instance?.id, {
 | 
						|
					interactiveBegan: false,
 | 
						|
					interactiveCancelled: true,
 | 
						|
				});
 | 
						|
				SharedTransition.notifyEvent(SharedTransition.interactiveCancelledEvent, {
 | 
						|
					id: state?.instance?.id,
 | 
						|
					type,
 | 
						|
				});
 | 
						|
			}, duration * 1000);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	static interactiveFinish(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType) {
 | 
						|
		if (state?.instance && interactiveState?.added && interactiveState?.propertyAnimator) {
 | 
						|
			interactiveState.propertyAnimator.reversed = false;
 | 
						|
 | 
						|
			const duration = isNumber(state.pageReturn?.duration) ? state.pageReturn?.duration / 1000 : CORE_ANIMATION_DEFAULTS.duration;
 | 
						|
			interactiveState.propertyAnimator.continueAnimationWithTimingParametersDurationFactor(null, duration);
 | 
						|
			setTimeout(() => {
 | 
						|
				for (const presenting of state.instance.sharedElements.presenting) {
 | 
						|
					presenting.view.opacity = presenting.startOpacity;
 | 
						|
					presenting.snapshot.removeFromSuperview();
 | 
						|
				}
 | 
						|
 | 
						|
				SharedTransition.finishState(state.instance.id);
 | 
						|
				interactiveState.propertyAnimator = null;
 | 
						|
				interactiveState.added = false;
 | 
						|
				interactiveState.transitionContext.finishInteractiveTransition();
 | 
						|
				interactiveState.transitionContext.completeTransition(true);
 | 
						|
				SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
 | 
						|
					id: state?.instance?.id,
 | 
						|
					type,
 | 
						|
					action: 'interactiveFinish',
 | 
						|
				});
 | 
						|
			}, duration * 1000);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |