From 15a9c23e4e37192af7e4a5a96b3c2234dfd84e59 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 24 Jan 2025 22:22:19 +0200 Subject: [PATCH 1/6] fix(ios): Proper iOS rotate animation --- packages/core/ui/animation/index.ios.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/core/ui/animation/index.ios.ts b/packages/core/ui/animation/index.ios.ts index 893ceadf9d..4b8cf2428c 100644 --- a/packages/core/ui/animation/index.ios.ts +++ b/packages/core/ui/animation/index.ios.ts @@ -517,21 +517,12 @@ export class Animation extends AnimationBase { } private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) { + const animations = NSMutableArray.alloc().initWithCapacity(3); const groupAnimation = CAAnimationGroup.new(); groupAnimation.duration = args.duration; - if (args.repeatCount !== undefined) { - groupAnimation.repeatCount = args.repeatCount; - } - if (args.delay !== undefined) { - groupAnimation.beginTime = CACurrentMediaTime() + args.delay; - } - if (animation.curve !== undefined) { - groupAnimation.timingFunction = animation.curve; - } - const animations = NSMutableArray.alloc().initWithCapacity(3); args.subPropertiesToAnimate.forEach((property) => { - const basicAnimationArgs = { ...args, duration: undefined, repeatCount: undefined, delay: undefined, curve: undefined }; + const basicAnimationArgs = { ...args }; basicAnimationArgs.propertyNameToAnimate = `${args.propertyNameToAnimate}.${property}`; basicAnimationArgs.fromValue = args.fromValue[property]; basicAnimationArgs.toValue = args.toValue[property]; @@ -676,11 +667,18 @@ export class Animation extends AnimationBase { result = CATransform3DScale(result, x === 0 ? 0.001 : x, y === 0 ? 0.001 : y, 1); } + if (value[Properties.rotate] !== undefined) { + const x = value[Properties.rotate].x; + const y = value[Properties.rotate].y; + const z = value[Properties.rotate].z; + result = iosHelper.applyRotateTransform(result, x, y, z); + } + return result; } private static _isAffineTransform(property: string): boolean { - return property === _transform || property === Properties.translate || property === Properties.scale; + return property === _transform || property === Properties.translate || property === Properties.scale || property === Properties.rotate; } private static _canBeMerged(animation1: PropertyAnimation, animation2: PropertyAnimation) { From 788bc9d0b53823c332e04e6cd5b646088ba153d0 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 24 Jan 2025 23:34:01 +0200 Subject: [PATCH 2/6] ref: More improvements on rotate animation --- .../src/ui/animation/animation-tests.ts | 10 ++--- packages/core/ui/animation/index.ios.ts | 38 ++++--------------- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/apps/automated/src/ui/animation/animation-tests.ts b/apps/automated/src/ui/animation/animation-tests.ts index ad75839ce4..7a96d2a7a5 100644 --- a/apps/automated/src/ui/animation/animation-tests.ts +++ b/apps/automated/src/ui/animation/animation-tests.ts @@ -79,7 +79,7 @@ export function test_PlayRejectsWhenAlreadyPlayingAnimation(done) { if (e === 'Animation is already playing.') { done(); } - } + }, ); } @@ -164,8 +164,8 @@ export function test_ChainingAnimations(done) { .then(() => label.animate({ translate: { x: 0, y: 0 }, duration: duration })) .then(() => label.animate({ scale: { x: 5, y: 5 }, duration: duration })) .then(() => label.animate({ scale: { x: 1, y: 1 }, duration: duration })) - .then(() => label.animate({ rotate: 180, duration: duration })) - .then(() => label.animate({ rotate: 0, duration: duration })) + .then(() => label.animate({ rotate: { x: 90, y: 0, z: 180 }, duration: duration })) + .then(() => label.animate({ rotate: { x: 0, y: 0, z: 0 }, duration: duration })) .then(() => { //console.log("Animation finished"); // >> (hide) @@ -610,7 +610,7 @@ export function test_PlayPromiseIsResolvedWhenAnimationFinishes(done) { function onRejected(e) { TKUnit.assert(false, 'Animation play promise should be resolved, not rejected.'); done(e); - } + }, ); } @@ -627,7 +627,7 @@ export function test_PlayPromiseIsRejectedWhenAnimationIsCancelled(done) { function onRejected(e) { TKUnit.assert(animation.isPlaying === false, 'Animation.isPlaying should be false when animation play promise is rejected.'); done(); - } + }, ); animation.cancel(); diff --git a/packages/core/ui/animation/index.ios.ts b/packages/core/ui/animation/index.ios.ts index 4b8cf2428c..d389f1468a 100644 --- a/packages/core/ui/animation/index.ios.ts +++ b/packages/core/ui/animation/index.ios.ts @@ -309,7 +309,6 @@ export class Animation extends AnimationBase { const parent = view.parent as View; let propertyNameToAnimate = animation.property; - let subPropertyNameToAnimate; let toValue = animation.value; let fromValue; if (nativeView) { @@ -347,30 +346,9 @@ export class Animation extends AnimationBase { style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.y; }; - propertyNameToAnimate = 'transform.rotation'; - subPropertyNameToAnimate = ['x', 'y', 'z']; - fromValue = { - x: nativeView.layer.valueForKeyPath('transform.rotation.x'), - y: nativeView.layer.valueForKeyPath('transform.rotation.y'), - z: nativeView.layer.valueForKeyPath('transform.rotation.z'), - }; - - if (animation.target.rotateX !== undefined && animation.target.rotateX !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) { - fromValue.x = (animation.target.rotateX * Math.PI) / 180; - } - if (animation.target.rotateY !== undefined && animation.target.rotateY !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) { - fromValue.y = (animation.target.rotateY * Math.PI) / 180; - } - if (animation.target.rotate !== undefined && animation.target.rotate !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) { - fromValue.z = (animation.target.rotate * Math.PI) / 180; - } - - // Respect only value.z for back-compat until 3D rotations are implemented - toValue = { - x: (toValue.x * Math.PI) / 180, - y: (toValue.y * Math.PI) / 180, - z: (toValue.z * Math.PI) / 180, - }; + propertyNameToAnimate = 'transform'; + fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform); + toValue = NSValue.valueWithCATransform3D(iosHelper.applyRotateTransform(nativeView.layer.transform, toValue.x, toValue.y, toValue.z)); break; case Properties.translate: animation._originalValue = { @@ -473,7 +451,6 @@ export class Animation extends AnimationBase { return { propertyNameToAnimate: propertyNameToAnimate, fromValue: fromValue, - subPropertiesToAnimate: subPropertyNameToAnimate, toValue: toValue, duration: duration, repeatCount: repeatCount, @@ -517,7 +494,7 @@ export class Animation extends AnimationBase { } private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) { - const animations = NSMutableArray.alloc().initWithCapacity(3); + const animations = NSMutableArray.alloc().initWithCapacity(args.subPropertiesToAnimate.length); const groupAnimation = CAAnimationGroup.new(); groupAnimation.duration = args.duration; @@ -946,9 +923,10 @@ function calculateTransform(view: View): CATransform3D { let expectedTransform = new CATransform3D(CATransform3DIdentity); // Only set perspective if there is 3D rotation - if (view.rotateX || view.rotateY) { - expectedTransform.m34 = -1 / perspective; - } + // TODO: Add perspective property to transform animations (not just rotation) + // if (view.rotateX || view.rotateY) { + // expectedTransform.m34 = -1 / perspective; + // } expectedTransform = CATransform3DTranslate(expectedTransform, view.translateX, view.translateY, 0); expectedTransform = iosHelper.applyRotateTransform(expectedTransform, view.rotateX, view.rotateY, view.rotate); From 4470761fcd1329de64a14b5ad7062c6fb89f2e8d Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 24 Jan 2025 23:49:37 +0200 Subject: [PATCH 3/6] chore: Added scale defaults --- packages/core/ui/animation/index.ios.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/ui/animation/index.ios.ts b/packages/core/ui/animation/index.ios.ts index d389f1468a..60b0c0390f 100644 --- a/packages/core/ui/animation/index.ios.ts +++ b/packages/core/ui/animation/index.ios.ts @@ -639,9 +639,9 @@ export class Animation extends AnimationBase { } if (value[Properties.scale] !== undefined) { - const x = value[Properties.scale].x; - const y = value[Properties.scale].y; - result = CATransform3DScale(result, x === 0 ? 0.001 : x, y === 0 ? 0.001 : y, 1); + const x = value[Properties.scale].x || 1e-6; + const y = value[Properties.scale].y || 1e-6; + result = CATransform3DScale(result, x, y, 1); } if (value[Properties.rotate] !== undefined) { From f4b3bbbf0b99abf65fc9dba759cb68ec32a332d4 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 24 Jan 2025 23:53:39 +0200 Subject: [PATCH 4/6] chore: Reverted changes on group animation --- packages/core/ui/animation/index.ios.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/core/ui/animation/index.ios.ts b/packages/core/ui/animation/index.ios.ts index 60b0c0390f..0286d9d908 100644 --- a/packages/core/ui/animation/index.ios.ts +++ b/packages/core/ui/animation/index.ios.ts @@ -498,8 +498,18 @@ export class Animation extends AnimationBase { const groupAnimation = CAAnimationGroup.new(); groupAnimation.duration = args.duration; + if (args.repeatCount !== undefined) { + groupAnimation.repeatCount = args.repeatCount; + } + if (args.delay !== undefined) { + groupAnimation.beginTime = CACurrentMediaTime() + args.delay; + } + if (animation.curve !== undefined) { + groupAnimation.timingFunction = animation.curve; + } + args.subPropertiesToAnimate.forEach((property) => { - const basicAnimationArgs = { ...args }; + const basicAnimationArgs = { ...args, duration: undefined, repeatCount: undefined, delay: undefined, curve: undefined }; basicAnimationArgs.propertyNameToAnimate = `${args.propertyNameToAnimate}.${property}`; basicAnimationArgs.fromValue = args.fromValue[property]; basicAnimationArgs.toValue = args.toValue[property]; From 002178f560ff88325d5fc24506eebca093cb1e93 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sat, 25 Jan 2025 00:01:05 +0200 Subject: [PATCH 5/6] chore: More scale default corrections --- packages/core/ui/animation/index.ios.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/ui/animation/index.ios.ts b/packages/core/ui/animation/index.ios.ts index 0286d9d908..3ced3e1465 100644 --- a/packages/core/ui/animation/index.ios.ts +++ b/packages/core/ui/animation/index.ios.ts @@ -79,8 +79,8 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate { targetStyle[setLocal ? widthProperty.name : widthProperty.keyframe] = value; break; case Properties.scale: - targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x === 0 ? 0.001 : value.x; - targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y === 0 ? 0.001 : value.y; + targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x === 0 ? 1e-6 : value.x; + targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y === 0 ? 1e-6 : value.y; break; case _transform: if (value[Properties.translate] !== undefined) { @@ -95,8 +95,8 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate { if (value[Properties.scale] !== undefined) { const x = value[Properties.scale].x; const y = value[Properties.scale].y; - targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = x === 0 ? 0.001 : x; - targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = y === 0 ? 0.001 : y; + targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = x === 0 ? 1e-6 : x; + targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = y === 0 ? 1e-6 : y; } break; } @@ -365,10 +365,10 @@ export class Animation extends AnimationBase { break; case Properties.scale: if (toValue.x === 0) { - toValue.x = 0.001; + toValue.x = 1e-6; } if (toValue.y === 0) { - toValue.y = 0.001; + toValue.y = 1e-6; } animation._originalValue = { x: view.scaleX, y: view.scaleY }; animation._propertyResetCallback = (value, valueSource) => { From ff194b5d07d4acc8d961e98bddbdc2b4458c293b Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sat, 25 Jan 2025 00:16:30 +0200 Subject: [PATCH 6/6] fix: Added perspective defaults for rotate animation since it makes use of z --- packages/core/ui/animation/index.ios.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/core/ui/animation/index.ios.ts b/packages/core/ui/animation/index.ios.ts index 3ced3e1465..fcc09327c4 100644 --- a/packages/core/ui/animation/index.ios.ts +++ b/packages/core/ui/animation/index.ios.ts @@ -658,6 +658,13 @@ export class Animation extends AnimationBase { const x = value[Properties.rotate].x; const y = value[Properties.rotate].y; const z = value[Properties.rotate].z; + const perspective = animation.target.perspective || 300; + + // Set perspective in case of rotation since we use z + if (x || y) { + result.m34 = -1 / perspective; + } + result = iosHelper.applyRotateTransform(result, x, y, z); } @@ -932,11 +939,11 @@ function calculateTransform(view: View): CATransform3D { // Order is important: translate, rotate, scale let expectedTransform = new CATransform3D(CATransform3DIdentity); - // Only set perspective if there is 3D rotation // TODO: Add perspective property to transform animations (not just rotation) - // if (view.rotateX || view.rotateY) { - // expectedTransform.m34 = -1 / perspective; - // } + // Set perspective in case of rotation since we use z + if (view.rotateX || view.rotateY) { + expectedTransform.m34 = -1 / perspective; + } expectedTransform = CATransform3DTranslate(expectedTransform, view.translateX, view.translateY, 0); expectedTransform = iosHelper.applyRotateTransform(expectedTransform, view.rotateX, view.rotateY, view.rotate);