Post not yet marked as solved
I'm trying to synchronize contents of a Metal layer with UIView animations (CAAnimation-based).
I've tried using custom CALayer animations by overriding the needsDisplay and action(forKey:) methods of CAMetalLayer, setting presentsWithTransaction to true, then performing a change inside a CATransaction. This works, but when animating a UIView and the contents of a CAMetalLayer side by side, the Metal contents slightly lags behind the UIView.
Is there any way to perfectly synchronize the app-side animated drawing with render server-side animations?
To me it looks like the safe area of a view is not updated after the owning view controller's .viewWillDisappear() method is called.
Is this intended or a bug in the framework?
The issue is easily visualised by creating a custom UIViewControllerTransitioningDelegate that animates a smaller view in one view controller, to full screen size in another (constrained to safe areas). Then the safe areas will expand as the present animation goes on (as expected), but will not shrink as the dismiss animation goes on (not expected!). The expected behaviour would be that the safe area expands during the present animation, and shrinks during the dismiss animation.
The gif below shows the unexpected behaviour. The grey area of the presented view controller is the safe area.
I've attached the code I used to visualise this problem. ViewController.swift presents MyViewController.swift using FullScreenTransitionManager.swift
FullScreenTransitionManager.swift
MyViewController.swift
ViewController.swift
So, straight to the problem:
I've created a custom UIViewControllerTransitioningDelegate that I use to animate a view from one view controller, to full-screen in another view controller. Im doing this by creating UIViewControllerAnimatedTransitioning-objects that animate the presented view's frame. And it works great! Except when I try to adjust the additionalSafeAreaInsets of the view controller owning the view during dismissal...
It looks like this property is not accounted for when I'm trying to animate the dismissal of the view controller and its view. It works fine during presentation.
So, what I want is: use additionalSafeAreaInsets to diminish the effect of the safe area during animation, by setting additionalSafeAreaInsets to the "inverted" values of the safe area. So that the effective safe area starts at 0 and "animates" to the expected value during presentation, and starts at expected value and "animates" to 0 during dismissal.
(I'm quoting "animates", since its actually the view's frame that is animated. But UIKit/Auto Layout use these properties when calculating the frames)
Any thoughts on how to battle this issue is great welcome!
The code for the custom UIViewControllerTransitioningDelegate is provided as an attachment.
FullScreenTransitionManager.swift
Post not yet marked as solved
how to add a core animation layer as it own composition track.
Post not yet marked as solved
I have a simple app that lets a user navigate a maze. A token is moved around the maze in response to input by accelerometer, digital joystick, taps, or keyboard entry, depending upon settings and device capabilities. The view to the maze may be zoomed, in which case the maze pans to center the token. The main animations are to move the token, and to recenter the maze. An additional animation occurs if the token encounters certain prizes along the way. When that happens, the prize award is displayed over the token and then animated toward the center of the board.
Randomly and rarely, the animation changes to be very slow, when running a build to the Mac. Unlike the iOS simulators, I know of no way to toggle the animation speed when building to the Mac. Is there a way to toggle this? Am I somehow toggling it accidentally? When I do a test run on the Mac, I'm almost always using keyboard input, which is the arrow keys.
Unlike the iOS simulators, where halting and restarting after the animation has been slowed results in continued slow animations, if I halt the Mac build after the animation slows down and start again, it animates at normal speed.
Post not yet marked as solved
I have MyLayer and it has a sublayer that it also holds a reference to. In that setup what is the proper way to override init(layer: Any)?
Right now I'm doing it like this ... layer.mySublayer.presentation()! is that correct? Is that always going to work, or will presentation ever be nil in this context?
Thanks for any tips!
class MyLayer: CALayer {
var mySublayer: CALayer
public override init() {
mySublayer = CALayer()
super.init()
addSublayer(mySublayer)
}
override init(layer: Any) {
let layer = layer as! MyLayer
mySublayer = layer.mySublayer.presentation()!
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("has not been implemented")
}
}
Is it possible to set a CALayer as the contents of a RealityKit material? Currently this is possible with SceneKit materials. I am wondering if there is something similar for RealityKit.
https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395372-contents
Post not yet marked as solved
I have a model CALayer that I animate.
I then remove that layer from its super layer.
I then wait for a long while
After this I'm surprised to see that my model layer still has a non-nil presentation layer. Is this expected behavior?
If this is not expected behavior what might make my model layers presentation layer stick around like that?
Thanks
Post not yet marked as solved
Since iOS 15.1, I am seeing the log message
CADisplayTimingsControl too many requests.
My app is SwiftUI based and yes, I do have one location in the code that uses CADisplayLink but I am fairly sure it is not the culprit.
I am seeing this during scrolling/list reloads in a SwiftUI app with a List/ForEach construct. At the same time I have multiple WKWebViews loading remote websites. I am not sure what the problem is but the app does lock up often with a deadlock in the main thread while none of my code is executed.
Any help with where this might be coming from is appreciated. I suspect it is a regression related to Pro Motion and/or SwiftUI an filed a radar including crash report as FB9731877.
Post not yet marked as solved
I want to get animation interpolation during animation running.
just like code below:
Animator(begin:100,end:200).addListener({value in
//here will be called when animation updates.
print(value)
})
But I can't find any API like the code above in iOS SDK.And I don't want to use any UIView or CALayer to get presentationLayer to get this value.
So How can I do that?🤔🤔🤔
Post not yet marked as solved
Hi, I have been getting the following crash in my app which has a Custom Metal Render Engine
CoreFoundation -[__NSSetM clumpingFactor] + 264
libobjc.A.dylib __objc_empty_cache + 888
CoreAutoLayout DA979160-E330-3C35-BF6F-D3248DCC3246 + 67536
CoreAutoLayout DA979160-E330-3C35-BF6F-D3248DCC3246 + 68272
UIKitCore __OBJC_$_INSTANCE_METHODS__UIDatePickerCalendarTimeLabel + 600
UIKitCore __OBJC_$_INSTANCE_METHODS__UINavigationBarStarkVisualStyle + 100
UIKitCore ___79+[UISwitchModernVisualElement _modernThumbImageWithColor:mask:traitCollection:]_block_invoke_2 + 204
UIKitCore -[UISwitchModernVisualElement _switchTrackPositionAnimationWithFromValue:toValue:on:] + 388
UIKitCore -[UISwitchModernVisualElement _effectiveGradientImage] + 128
UIKitCore __OBJC_$_INSTANCE_METHODS__UISearchBarVisualProviderLegacy + 1924
QuartzCore CA::Layer::add_animation(CAAnimation*, __CFString const*) + 72
QuartzCore CA::Layer::remove_sublayer(CA::Transaction*, CALayer*) + 272
QuartzCore CA::OGL::Context::draw_elements(CA::OGL::PrimitiveMode, unsigned int, unsigned short const*, CA::OGL::Vertex const*, unsigned int, unsigned int, CA::OGL::ClipPlane const*) + 60
QuartzCore CAML::cgcolor_end(CAML::Context*, CAML::State*, char*, unsigned long) + 1252
QuartzCore native_window_swap(_EAGLNativeWindowObject*, unsigned int, double) + 712
QuartzCore -[CAStateControllerAnimation initWithLayer:key:] + 52
CoreFoundation ___CFSocketSetSocketReadBufferAttrs + 444
CoreFoundation __CFNonObjCEqual + 8
CoreFoundation __CFRelease + 952
Foundation 4E7D1FF6-6B64-3833-9E60-CC662AFE2647 + 36236
danmu -[DMEngineBase runMetalThread] (in DMEngineBase.mm:148)
Foundation 4E7D1FF6-6B64-3833-9E60-CC662AFE2647 + 1549068
libsystem_pthread.dylib _pthread_rwlock_unlock$VARIANT$armv81 + 160
libsystem_pthread.dylib __pthread_create + 1196
this crash seems caused by the Metal Thread Rendering which triggered a CoreAnimation drawing and finally crashed at CoreAutoLayout internal method :
Crashed View : appEnterForeground
Exception Name : NSInternalInconsistencyException
Exception Reason :
Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.
In the latest Version I have been trying solved this crash by add some protected like this :
- (void)runMetalThread
{
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[_displayLink addToRunLoop:runLoop forMode:DMMetalRunLoopModelFrame];
BOOL continueRunLoop = YES;
while (continueRunLoop)
{
@autoreleasepool
{
[runLoop runMode:DMMetalRunLoopModelFrame beforeDate:[NSDate distantFuture]];
}
continueRunLoop = _continueRunLoop;
}
}
- (void)onBulletDraw:(CADisplayLink*)displayLink
{
self.renderer.stoped = !_isActive || _stoped;
self.renderer.paused = _isPaused;
[self.renderer onDanmuDraw:displayLink];
}
#pragma mark - Notification
- (void)willResignActive
{
self.isActive = NO;
MTLog(@"metal# engine %@ resign active", self);
}
- (void)didBecomeActive
{
//Protected by delay active the metal engine after app state didBecomeActive
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) {
self.isActive = YES;
MTLog(@"metal# engine %@ become active", self);
}else{
MTLog(@"metal# engine still inactive after 0.7s");
}
});
}
this protected has make sure to STOPed the Metal Render after the app resign active, and
seems has some effect which reduced 84% crashes on my released app . but it did not completely solve this problem still has 16% crashes in some unknown scene .
I have been checked all my Metal Rendering thread code , I can be guaranteed that no method will trigger CoreAnimation drawing in this metal thread. So is there has an Perfective Solution of this bug ?
Code to draw a graph of data. Each abscissa has an ordinate range to be displayed as a line segment.
All data, i.e., scaled points are verified to be within the declared analysisView.bounds. strokeColors are verified within the range 0..1
BTW, no I don't need animation for this static data, but CALayer seemed to require more coding, and I found fewer code examples for it.
The code below has two problems:
1) it doesn't draw into the window
the weird behavior of min/max
The first is why I am posting. What am I missing?
import AppKit
class AnalysisViewController: NSViewController {
@IBOutlet var analysisView: NSView!
var ranges = [ClosedRange<Double>]()
var ordinateMinimum = CGFloat()
var ordinateMaximum = CGFloat()
var ordinateScale = CGFloat()
let abscissaMinimum:CGFloat = 1
let abscissaMaximum:CGFloat = 92
let abscissaScale :CGFloat = 800/92
let shapeLayer = CAShapeLayer()
var points = [CGPoint]() // created just to verify (in debugger area) that points are within analysisView.bounds
func genrateGraph() {
// ranges.append(0...0) // inexplicably FAILS! @ ordinateMinimum/ordinateMaximum if replaces "if N == 1" below
// ranges.append(0.1...0.1) // non-zero range does not fail but becomes the min or max, therefore, not useful
for N in 1...92 {
if let element = loadFromJSON(N) {
if N == 1 { ranges.append( element.someFunction() ) } // ranges[0] is an unused placeholder
// if N == 1 { ranges.append(0...0) } // inexplicably FAILS! @ ordinateMinimum/ordinateMaximum if replacing above line
ranges.append( element.someFunction() )
}
else { ranges.append(0...0) } // some elements have no range data
}
ordinateMinimum = CGFloat(ranges.min(by: {$0 != 0...0 && $1 != 0...0 && $0.lowerBound < $1.lowerBound})!.lowerBound)
ordinateMaximum = CGFloat(ranges.max(by: {$0 != 0...0 && $1 != 0...0 && $0.upperBound < $1.upperBound})!.upperBound)
ordinateScale = analysisView.frame.height/(ordinateMaximum - ordinateMinimum)
for range in 1..<ranges.count {
shapeLayer.addSublayer(CALayer()) // sublayer each abscissa range so that .strokeColor can be assigned to each
// shapeLayer.frame = CGRect(x: 0, y: 0, width: analysisView.frame.width, height: analysisView.frame.height) // might be unneccessary
let path = CGMutablePath() // a new path for every sublayer, i.e., range that is displayed as line segment
points.append(CGPoint(x: CGFloat(range)*abscissaScale, y: CGFloat(ranges[range].lowerBound)*ordinateScale))
path.move(to: points.last! )
points.append(CGPoint(x: CGFloat(range)*abscissaScale, y: CGFloat(ranges[range].upperBound)*ordinateScale))
path.addLine(to: points.last! )
path.closeSubpath()
shapeLayer.path = path
// shapeLayer.strokeColor = CGColor.white
let r:CGFloat = 1.0/CGFloat(range)
let g:CGFloat = 0.3/CGFloat(range)
let b:CGFloat = 0.7/CGFloat(range)
// print("range: \(range)\tr: \(r)\tg: \(g)\tb: \(b)") // just to verify 0...1 values
shapeLayer.strokeColor = CGColor(srgbRed: r, green: g, blue: b, alpha: 1.0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.wantsLayer = true // one of these (view or analysisView) must be unneccessary
view.frame = CGRect(x: 0, y: 0, width: 840, height: 640)
analysisView.wantsLayer = true
analysisView.frame = CGRect(x: 0, y: 0, width: 840, height: 640)
genrateGraph()
}
}
Post not yet marked as solved
Hello all!
In my project, I have created a swipeable carousel view that changes a published property (currentHoleIdx) in my ViewModel that I use to programmatically change a TabView.
I want the TabView to animate when the index changes, and it does, but only with the deprecated .animated(.easeInOut).
I have tried .animation(.easeInOut, value:model.currentHoleIdx) and also tried withAnimation when I change the currentHoleIdx property.
I will do my best to post the code to show what I have done. Thanks in advance for any help!
TabView with animation
VStack {
ZStack(alignment: .top) {
// Mini Scorecard
MiniScorecard(scorecard:model.scorecards[model.currentScorecardIdx!], teams: model.scorecards[model.currentScorecardIdx!].teams)
.coordinateSpace(name: "mini")
// .opacity(showScore ? 1 : 0)
.background(Color("card"))
.background(
GeometryReader { geo in
Color.clear.onAppear {
showScoreOffset = geo.size.height
// print(geo.size.height)
}
}
)
// Scorecard TabView
TabView(selection: $model.currentHoleIdx,
content: {
ForEach(0..<model.scorecards[model.currentScorecardIdx!].holes.count) {idx in
let scorecard = model.scorecards[model.currentScorecardIdx!]
if scorecard.formatType == "individual" {
if scorecard.gameID == "Standard" {
IndivStandardScoreScrollview(idx:idx)
.tag(idx)
.animation(.easeInOut, value: model.currentHoleIdx) // // does not work does not work with idx or model.currentHoleIdx
}
} else {
if scorecard.gameID == "Standard" {
TeamStandardScoreScrollview(holeIdx:idx)
.tag(idx)
.animation(.easeInOut, value: idx) // does not work does not work with idx or model.currentHoleIdx
}
}
}
Color.clear
.tag(model.scorecards[model.currentScorecardIdx!].holes.count)
}).tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.background(Color("card"))
.offset(y:showScore ? showScoreOffset : 0)
.animation(.easeInOut, value: showScore)
.animation(.easeInOut, value: scorecardOffset)
// .animation(.easeInOut, value: model.currentHoleIdx) // does not work
}.frame(width:UIScreen.main.bounds.width - 30)
}
.offset(y: scorecardOffset)
.offset(y: model.currentHoleIdx < model.scorecards[model.currentScorecardIdx!].holes.count ? 0 : scorecardHeight)
.padding(.top)
.padding(.horizontal, 15)
.animation(.easeInOut, value: scorecardOffset)
.animation(.easeInOut, value: model.currentHoleIdx) // does not work!!!
// .animation(.easeInOut) // Only thing that works! when model.currentHoleIdx changes from drag gesture
.overlay(
GeometryReader { geo in
Color.clear.onAppear {
scorecardHeight = geo.size.height
}
}
)
Swipeable carousel drag gesture - withAnimation when interacting with model.currentHoleIdx
return HStack(alignment: .center, spacing: spacing) {
items
}
.offset(x: CGFloat(calcOffset), y: 0)
.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .named("canvas")).updating($screenDrag) { dragValue, gestureState, transaction in
// self.model.screenDrag = Float(currentState.translation.width)
// gestureState == screenDrag
gestureState = dragValue.translation
}.onEnded { value in
// screenDrag = 0 - Don't need this, bc GestureState automatically resets back to initial value
// Swipe to next hole
if (value.translation.width < -15) && self.model.currentHoleIdx < Int(numberOfItems) - 1 {
// calculate currentholeidx based on swipe width and cardwidth
let calculatedHoleIdx = self.model.currentHoleIdx - Int(floor(value.translation.width/(cardWidth + spacing + 10)))
withAnimation { // withAnimation Here
self.model.currentHoleIdx = calculatedHoleIdx > Int(numberOfItems - 1) ? Int(numberOfItems - 1) : calculatedHoleIdx
}
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
}
// Swipe to previous hole
if (value.translation.width > 15) && self.model.currentHoleIdx > 0 {
let calculatedHoleIdx = self.model.currentHoleIdx - Int(ceil((value.translation.width/(cardWidth + spacing + 10))))
withAnimation { // withAnimation Here
self.model.currentHoleIdx = calculatedHoleIdx < 0 ? 0 : calculatedHoleIdx
}
self.model.currentHoleIdx = calculatedHoleIdx < 0 ? 0 : calculatedHoleIdx
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
}
I feel like i've tried every possible iteration of .animation and withAnimation. The only one that works is using the deprecated .animation() modifier. Hope to hear from someone soon. thanks again!
Post not yet marked as solved
I have a CALayer with many sublayers. Those sublayers have multiple CABasicAnimation added to them.
Now, I'd like to render the whole layer subtree to the UIImage at a specific point of animation time. How could I achieve that?
The only thing I found is a CALayer.render(in:) method but the docs say that this method ignores Core Animations :<
Post not yet marked as solved
I'm trying to add an animated CALayer over my video and export it with AVAssetExportSession.
I'm animating the layer using CABasicAnimation set to my custom property.
However, it seems that func draw(in ctx: CGContext) is never called during an export for my custom layer, and no animation is played.
I found out that animating standard properties like borderWidth works fine, but custom properties are ignored.
Can someone help with that?
func export(standard: Bool) {
print("Exporting...")
let composition = AVMutableComposition()
//composition.naturalSize = CGSize(width: 300, height: 300)
// Video track
let videoTrack = composition.addMutableTrack(withMediaType: .video,
preferredTrackID: CMPersistentTrackID(1))!
let _videoAssetURL = Bundle.main.url(forResource: "emptyVideo", withExtension: "mov")!
let _emptyVideoAsset = AVURLAsset(url: _videoAssetURL)
let _emptyVideoTrack = _emptyVideoAsset.tracks(withMediaType: .video)[0]
try! videoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: _emptyVideoAsset.duration),
of: _emptyVideoTrack, at: .zero)
// Root Layer
let rootLayer = CALayer()
rootLayer.frame = CGRect(origin: .zero, size: composition.naturalSize)
// Video layer
let video = CALayer()
video.frame = CGRect(origin: .zero, size: composition.naturalSize)
rootLayer.addSublayer(video)
// Animated layer
let animLayer = CustomLayer()
animLayer.progress = 0.0
animLayer.frame = CGRect(origin: .zero, size: composition.naturalSize)
rootLayer.addSublayer(animLayer)
animLayer.borderColor = UIColor.green.cgColor
animLayer.borderWidth = 0.0
let key = standard ? "borderWidth" : "progress"
let anim = CABasicAnimation(keyPath: key)
anim.fromValue = 0.0
anim.toValue = 50.0
anim.duration = 6.0
anim.beginTime = AVCoreAnimationBeginTimeAtZero
anim.isRemovedOnCompletion = false
animLayer.add(anim, forKey: nil)
// Video Composition
let videoComposition = AVMutableVideoComposition(propertiesOf: composition)
videoComposition.renderSize = composition.naturalSize
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
// Animation tool
let animTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: video,
in: rootLayer)
videoComposition.animationTool = animTool
// Video instruction > Basic
let videoInstruction = AVMutableVideoCompositionInstruction()
videoInstruction.timeRange = CMTimeRange(start: .zero, duration: composition.duration)
videoComposition.instructions = [videoInstruction]
// Video-instruction > Layer instructions
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
videoInstruction.layerInstructions = [layerInstruction]
// Session
let exportSession = AVAssetExportSession(asset: composition,
presetName: AVAssetExportPresetHighestQuality)!
exportSession.videoComposition = videoComposition
exportSession.shouldOptimizeForNetworkUse = true
var url = FileManager.default.temporaryDirectory.appendingPathComponent("\(arc4random()).mov")
url = URL(fileURLWithPath: url.path)
exportSession.outputURL = url
exportSession.outputFileType = .mov
_session = exportSession
exportSession.exportAsynchronously {
if let error = exportSession.error {
print("Fail. \(error)")
} else {
print("Ok")
print(url)
DispatchQueue.main.async {
let vc = AVPlayerViewController()
vc.player = AVPlayer(url: url)
self.present(vc, animated: true) {
vc.player?.play()
}
}
}
}
}
CustomLayer:
class CustomLayer: CALayer {
@NSManaged var progress: CGFloat
override init() {
super.init()
}
override init(layer: Any) {
let l = layer as! CustomLayer
super.init(layer: layer)
print("Copy. \(progress) \(l.progress)")
self.progress = l.progress
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override class func needsDisplay(forKey key: String) -> Bool {
let needsDisplayKeys = ["progress"]
if needsDisplayKeys.contains(key) {
return true
}
return super.needsDisplay(forKey: key)
}
override func display() {
print("Display. \(progress) | \(presentation()?.progress)")
super.display()
}
override func draw(in ctx: CGContext) {
// Save / restore ctx
ctx.saveGState()
defer { ctx.restoreGState() }
print("Draw. \(progress)")
ctx.move(to: .zero)
ctx.addLine(to: CGPoint(x: bounds.size.width * progress,
y: bounds.size.height * progress))
ctx.setStrokeColor(UIColor.red.cgColor)
ctx.setLineWidth(40)
ctx.strokePath()
}
}
Here's a full sample project if someone is interested:
https://www.dropbox.com/s/evkm60wkeb2xrzh/BrokenAnimation.zip?dl=0
Post not yet marked as solved
Hi there,
I was wondering whether its possible, in Reality Composer, to add a mesh object into the scene with an animated texture onto it? Whether it's a sprite sheet or a simple tiling offset animation?
If not, is there a way to switch textures that are on an object? Either immediately on tap, or gradually fade from one to the other.
Post not yet marked as solved
I have a MacOS application which displays a NSStatusItem with a custom view, implemented in SwiftUI. I noticed some rather terrible performance problems, however only when I am using some specific wallpapers.
I've made a super small Xcode project - https://github.com/nmcdonaldd/StatusItemCACommitCycle demoing the behavior. In that repo, I've also linked a YouTube video demoing the behavior (can't post video link here for some reason).
With one wallpaper, Instruments Time Profile is showing little-to-no weight in any CA-related traces. However, as soon as I change the wallpaper, Instruments is reporting a huge increase in CA::Transaction::commit(). After updating the wallpaper, Core Animation commit graph in instruments is showing hundreds of commits per second. When I switch it back to the first wallpaper, it stops. Also interesting - as soon as I switch the wallpaper, Instruments Time Profiler is reporting my application to be out of "Initializing" state and now into "Foreground".
Is this a bug or something I, as a developer, can prevent? Furthermore, how would a wallpaper change the state of my application?
Post not yet marked as solved
Hi Expert,
When I launched my App, just saw the following crash once:
crash log
Looks like there is something dead loop in the system libraries? related to NSNotificationCenter? any clue on this? Thanks a lot.
Reposting for better tags. Please see the details for the original post:
https://developer.apple.com/forums/thread/688268
I tried to pair the CATransaction.begin() and CATransaction.commit() but it does not work. Maybe I am doing it wrong.
Post not yet marked as solved
I have a keyframe animation that animates an indicator in a control. Picture something like a speedometer in an arc where a pointer animates along the top.
I set rotationMode on the animation to kCAAnimationRotateAuto and the animation itself works perfectly. When the animation completes, the pointer blinks back to a zero angle rotation. I want it to stay in the rotation it was in at the end of the animation.
I tried using animationDidStop:finished: to set my layer's transform to the desired angle with layer.transform = CATransform3DMakeRotation(angle, 0, 0, 1) at the end of the animation which does set the correct rotation angle at the end of the animation, but then the animation itself has an incorrect rotation.
I tried setting the layer's transform before the animation starts thinking the animation would animate to that value, but the animation was even more bonkers.
Not sure what else to try. The rotationMode works beautifully, except for the end state.