Core Animation

RSS for tag

Render, compose, and animate visual elements using Core Animation.

Posts under Core Animation tag

47 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Relationship between CABasicAnimation's KeyPath and a CALayer's action event
I have a CALayer and I'd like to animate a property on it. But, the property that triggers the animation change is different to the one that is being changed. A basic example of what I'm trying to do is below. I'm trying to create an animation on count by changing triggerProperty. This example is simplified (in my project, the triggerProperty is not an Int, but a more complex non-animatable type. So, I'm trying to animate it by creating animations for some of it's properties that can be matched to CABasicAnimation - and rendering a version of that class based on the interpolated values). @objc class AnimatableLayer: CALayer { @NSManaged var triggerProperty: Int @NSManaged var count: Int override init() { super.init() triggerProperty = 1 setNeedsDisplay() } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override class func needsDisplay(forKey key: String) -> Bool { return key == String(keypath: \AnimatableLayer.triggerProperty) || super.needsDisplay(forKey: key) } override func action(forKey event: String) -> (any CAAction)? { if event == String(keypath: \AnimatableLayer.triggerProperty) { if let presentation = self.presentation() { let keyPath = String(keypath: \AnimatableLayer.count) let animation = CABasicAnimation(keyPath: keyPath) animation.duration = 2.0 animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) animation.fromValue = presentation.count animation.toValue = 10 return animation } } return super.action(forKey: event) } override func draw(in ctx: CGContext) { print("draw") NSGraphicsContext.saveGraphicsState() let nsctx = NSGraphicsContext(cgContext: ctx, flipped: true) // create NSGraphicsContext NSGraphicsContext.current = nsctx // set current context let renderText = NSAttributedString(string: "\(self.presentation()?.count ?? self.count)", attributes: [.font: NSFont.systemFont(ofSize: 30)]) renderText.draw(in: bounds) NSGraphicsContext.restoreGraphicsState() } func animate() { print("animate") self.triggerProperty = 10 } } With this code, the animation isn't triggered. It seems to get triggered only if the animation's keypath matches the one on the event (in the action func). Is it possible to do something like this?
1
0
120
2w
Why does the planeNode in SceneKit flicker when using class property instead of a local variable?
I am working on a SceneKit project where I use a CAShapeLayer as the content for SCNMaterial's diffuse.contents to display a progress bar. Here's my initial code: func setupProgressWithCAShapeLayer() { let progressLayer = createProgressLayer() progressBarPlane?.firstMaterial?.diffuse.contents = progressLayer DispatchQueue.main.async { var progress: CGFloat = 0.0 Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in progress += 0.01 if progress > 1.0 { progress = 0.0 } progressLayer.strokeEnd = progress // Update progress } } } // MARK: - ARSCNViewDelegate func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { progressBarPlane = SCNPlane(width: 0.2, height: 0.2) setupProgressWithCAShapeLayer() let planeNode = SCNNode(geometry: progressBarPlane) planeNode.position = SCNVector3(x: 0, y: 0.2, z: 0) node.addChildNode(planeNode) } This works fine, and the progress bar updates smoothly. However, when I change the code to use a class property (self.progressLayer) instead of a local variable, the rendering starts flickering on the screen: func setupProgressWithCAShapeLayer() { self.progressLayer = createProgressLayer() progressBarPlane?.firstMaterial?.diffuse.contents = progressLayer DispatchQueue.main.async { [weak self] in var progress: CGFloat = 0.0 Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in progress += 0.01 if progress > 1.0 { progress = 0.0 } self?.progressLayer?.strokeEnd = progress // Update progress } } } After this change, the progressBarPlane in SceneKit starts flickering while being rendered on the screen. My Question: Why does switching from a local variable (progressLayer) to a class property (self.progressLayer) cause the flickering issue in SceneKit rendering?
0
0
136
2w
Which Apple technologies to use for simple 2d motion graphics software?
I plan to create a simple motion graphics software for macOS that animates text, basic shapes, and handles audio. I'll use SwiftUI for the UI. What are the commonly used technologies for rendering animated graphics? Core Animation is suitable for UI animations but not for exporting and controlling UI animations. Basic requirements: Timeline user interface Animation of text and basic shapes Viewer in SwiftUI GUI with transport control (play, pause, scrub, …) Export to video file Is Metal or Core Graphics typically used directly? I want to keep it as simple as possible.
1
0
241
3d
When using the animation, the CPU usage rises to around 20-25%.
I'm new to developing with SwiftUI and I created a Pomodoro app for macOS that runs in the menu bar. I added 4 animations and when the user selects the snow animation, it starts snowing on the screen. But the app uses 20%-30% of the CPU and has high energy consumption. I can't reduce it and I couldn't find a solution. // snow animation import SwiftUI struct SnowflakeView: View { @State private var flakeYPosition: CGFloat = -100 @State private var isAnimating = false private let flakeSize: CGFloat = CGFloat.random(in: 10...30) private let flakeColor: Color = Color( red: Double.random(in: 0.8...1), green: Double.random(in: 0.9...1), blue: Double.random(in: 1...1), opacity: Double.random(in: 0.6...0.8) ) private let animationDuration: Double = Double.random(in: 1...3) private let flakeXPosition: CGFloat = CGFloat.random(in: 0...310) var body: some View { Text("❄️") .font(.system(size: flakeSize)) .foregroundColor(flakeColor) .position(x: flakeXPosition, y: flakeYPosition) .onAppear { if !isAnimating { withAnimation(Animation.linear(duration: animationDuration).repeatForever(autoreverses: false)) { flakeYPosition = 280 + 50 } isAnimating = true } } } } I also have how I run the animation below. ZStack { ForEach(0..<10, id: \.self) { index in if selectedAnimal == "Snow" { SnowflakeView() } else if selectedAnimal == "Rain" { RainDropAnimation() }else if selectedAnimal == "Leaf"{ LeafFallAnimation() }else if selectedAnimal == "Confetti"{ ConfettiAnimation() } } }
0
0
226
3w
App Crashes on QuartzCore: CA::Layer::layout_if_needed(CA::Transaction*) + 504
I have facing an above crash for many users device running on iOS 17.6.1 mostly on iPad devices. I'm not sure why this happening only in 17.X. In Xcode Organizer unable to see this crash in any devices running on OS 18.x. Our app crashes got spiked due to this. I am unable to fix or reproduce the same. The crash log is not pointing to our app code to find the root cause and fix this issue. Have attached the crash log in this post also the crash log roles have mixed values Background &amp;amp; Foreground. But most of the crash is in background. Is this any crash related to system and that only solved by OS update? I have updated the app using Xcode 16 and 16.1 still facing this crash unable to symbolicate the crash report as well. Any ideas/solution how to solve this or how to proceed further. Have attached the entire crash log below. RoleBackgroundCrash.crash RoleForeGroundCrash.crash
0
0
187
Nov ’24
CPU use increases over time with simple animation
I created a simple animation of a circle that changes sizes. The circle pulses like a heartbeat in the center of the screen. My expectation was for the CPU use to be very low, but that is not the case. In addition, even if the CPU use isn't as low as I would expect, I did not expect the CPU use to increase over time because nothing else is happening in the app. Here is the code: import SwiftUI @main struct TestApp: App { var body: some Scene { WindowGroup { SplashScreenView() } } } import SwiftUI struct SplashScreenView: View { var body: some View { ZStack { SplashNucleusView(minSize: 50, maxSize: 100) } } } import SwiftUI struct SplashNucleusView: View { let minSize: Double let maxSize: Double @State private var nucleusColor: Color = .primary @State private var nucleusRadius: Double = 10 @State private var nucleusOpacity: Double = 1.0 private var nucleusAnimation: Animation { .easeInOut(duration: 0.25) .repeatForever(autoreverses: true) } let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect() var body: some View { Circle() .fill(nucleusColor) .frame(width: nucleusRadius) .opacity(nucleusOpacity) .onReceive(timer) { _ in withAnimation(nucleusAnimation) { nucleusRadius = Double.random(in: minSize...maxSize) } } } } This is how the animation looks: The animation is snappy until the CPU use reaches 95%, at which point there is visible stuttering. Here is how the CPU looks when the animation duration value is 0.5 seconds and the timer publishing interval is 3 seconds: Changing the animation duration value to 0.25 seconds and the timer publishing interval to 0.5 seconds changes the CPU use as follows: The complete view has many other moving parts, which make the issue much worse. The issue is evident with only the circle view. I spent hours working with the debugger, reading about animations, and testing new approaches, but the result is always the same. Why is this happening?
2
0
244
Oct ’24
CAMetalDisplayLink does not work on separate thread
I am looking to implement CAMetalDisplayLink on a separate thread on a macOS application. I am basing my implementation on the following example project: Achieving Smooth Frame Rates with Metal Display Link This project allows you to configure whether a separate thread is used for rendering by setting RENDER_ON_MAIN_THREAD in GameConfig to 0. However, when I set it to use a separate thread nothing is rendered. Stepping through the code shows that a separate thread is created, but a CAMetalDisplayLinkUpdate is never received. Does anyone know why this does not work?
1
0
279
Oct ’24
Navigation Bar animation upon Tab change
Hi. Since Xcode 16 and/or iOS 18.0 (I upgraded at the same time), I have an strange effect in the lower (let's say) 20% section of the Navigation Bar when changing to another tab, and this independently if large titles are used or not. Mentioned section is brighter or darker than the rest of the Navigation Bar background, depending on which background tint is used. This effect lasts about 0.3 seconds, but is clearly visible, quite disturbing and new as of Xcode 16 and/or iOS 18.0. I use the code below in AppDelegate to get a gradient coloured Navigation Bar background. let appearance = UINavigationBarAppearance() UINavigationBar.appearance().standardAppearance = appearance UINavigationBar.appearance().compactAppearance = appearance UINavigationBar.appearance().scrollEdgeAppearance = appearance UINavigationBar.appearance().compactScrollEdgeAppearance = appearance If I don't use above code., the background color is filled and without gradient. Subject effect doesn't show in this case. The effect basically looks like when changing tab, the new Navigation Bar background doesn't clear right away, and keeps the background from the previous Navigation Bar for 0.3 seconds before new one Navigation Bar background is rendered. I spent quite some time on changing every possible setting, in code as well as storyboard ... no success so far. Any ideas how to disable this undesired animation?
0
0
237
Oct ’24
SceneKit Animations Transition Abruptly on iOS 18 Device, but Smooth in Simulator
Hi Friends! I’m facing an issue with SceneKit. I’m developing a 3D mobile game. I have a character 3D model and several skeletal animations CAAnimation. I import both the model and the animations from Maya in *.dae format. The character’s animations play continuously one after the other, with each new animation being triggered randomly. The transition between animations makes smoothly by setting the fadeInDuration and fadeOutDuration properties. Here’s an example of the code: import UIKit import QuartzCore import SceneKit class TestAnimationController: UIViewController { var bodyNode: SCNNode? override func viewDidLoad() { super.viewDidLoad() let scnView = SCNView(frame: self.view.bounds) scnView.backgroundColor = .black // Set your desired background color scnView.autoresizingMask = [.flexibleWidth, .flexibleHeight] let scene = SCNScene(named: "art.scnassets/scene/Base_room/ROOM5.scn")! bodyNode = collada2SCNNode(filepath: "art.scnassets/female/girl_body_races.dae")! bodyNode?.renderingOrder = 10 scene.rootNode.addChildNode(bodyNode!) playIdleAnimation() scnView.scene = scene // Assign the scene to the SCNView self.view.addSubview(scnView) // Add the SCNView to your main view) } func collada2SCNNode(filepath:String) -> SCNNode? { if let scene = SCNScene(named: filepath) { let node = scene.rootNode.childNodes[0] return node } else { return nil } } func playIdleAnimation() { let array = [ "art.scnassets/female/animations/idle/girl_idle_4.dae", "art.scnassets/female/animations/idle/girl_idle1.dae", "art.scnassets/female/animations/idle/girl_idle2.dae", "art.scnassets/female/animations/idle/Girl_idle3.dae", ] let animation = CAAnimation.animationWithSceneNamed(array.randomElement() ?? "")! self.setAnimationAdd( fadeInDuration: 1.0, fadeOutDuration: 1.0, keyTime: 0.99, animation, isLooped: false ) { [weak self] in guard let self = self else { return } try? self.playBoringAnimations() } } func playBoringAnimations() { let array = [ "art.scnassets/female/animations/boring/girl_boring1.dae", "art.scnassets/female/animations/boring/girl_boring2.dae", "art.scnassets/female/animations/boring/girl_boring3.dae", "art.scnassets/female/animations/boring/girl_boring4.dae", "art.scnassets/female/animations/boring/girl_boring5.dae", "art.scnassets/female/animations/boring/girl_boring6.dae", "art.scnassets/female/animations/boring/girl_boring8.dae" ] let animation = CAAnimation.animationWithSceneNamed(array.randomElement() ?? "")! self.setAnimationAdd( fadeInDuration: 1.0, fadeOutDuration: 1.0, keyTime: 0.99, animation, isLooped: false ) { [weak self] in guard let self = self else { return } try? self.playIdleAnimation() } } func setAnimationAdd(fadeInDuration : CGFloat, fadeOutDuration : CGFloat, keyTime : CGFloat, _ animation: CAAnimation, isLooped: Bool, completion: (() -> Void)?) { animation.fadeInDuration = fadeInDuration animation.fadeOutDuration = fadeOutDuration if !isLooped { animation.repeatCount = 1 } else { animation.repeatCount = Float.greatestFiniteMagnitude } animation.animationEvents = [ SCNAnimationEvent(keyTime: keyTime, block: { _, _, _ in completion?() }) ] bodyNode?.addAnimation(animation, forKey: "avatarAnimation") } } Everything worked perfectly until I updated to iOS 18. On a physical device, the animations now transition abruptly without the smooth blending that was present in earlier iOS versions. The switch between them is very noticeable, as if the fadeInDuration and fadeOutDuration parameters are being ignored. However, in the iOS 18 simulator, the animations still transition smoothly as before. Here two example videos - IOS 17.5 and IOS 18 https://youtube.com/shorts/jzoMRF4skAQ - IOS 17,5 smooth https://youtube.com/shorts/VJXrZzO9wl0 - IOS 18 not smooth I try this code in IOS 17.5, everything works fine Does anyone have any ideas on how to fix this issue?
0
0
302
Oct ’24
How is CAShapeLayer implemented
Hello, I want to create a painting app for iOS and I saw many examples use a CAShapeLayer to draw a UIBezierPath. As I understand CoreAnimation uses the GPU so I was wondering how is this implemented on the GPU? Or in other words, how would you do it with Metal or OpenGL? I can only think of continuously updating a texture in response to the user's drawing but that would be a very resource intensive operation... Thanks
3
0
484
Sep ’24
The elements in the attachment cannot add translation.
UI: Attachment(id: "tooptip") { if isRecording { TooltipView { HStack(spacing: 8) { Image(systemName: "waveform") .font(.title) .frame(minWidth: 100) } } .transition(.opacity.combined(with: .scale)) } } Trigger: Button("Toggle") { withAnimation{ isRecording.toggle() } } The above code did not show the animation effect when running. When I use isRecording to drive an element in a common SwiftUI view, there is an animation effect.
0
0
330
Aug ’24
Save UIImage with CATransform3D applied
I have a 3х3 Matrix which I need to apply to UIImage and save it in Documents folder. I successfully converted the 3x3 Matrix (represented as [[Double]]) to CATrasform3D and then I have broken my head with trying to figure out how to apply it to UIImage. The only property where I can I apply it is UIView(or UIImageView in case with working with UIImage) transform property. But it has nothing to do with UIImage itself. I can't save the UIImage from transformed the UIImageView with all the transformations. And all the CoreGraphic methods (like concatenate for CGContext) only work with affine transformations which not suits for me. Please give me a hint what direction I should look. Does Apple has native methods or I have to use 3rd party frameworks for this functionality?
1
0
418
Aug ’24
CADisplayLink wrongly capping at 90hz on iPhone 15 Pro
I have a game for iOS where I use CADisplayLink to animate a simulation, and for some reason the animation is not getting the full 120hz on capable devices (like iPhone 15 Pro). When I enable a 120hz refresh target, the animation is capped at only 90hz. This looks terrible because the animation works best when doubled (30, 60, 120, 240, etc). The really bizarre thing is that when I turn on Screen Recording, my frame rate instantly jumps to 120, and everything looks perfectly smooth. My game has never looked better on iPhone! When recording is stopped, the animation drops back down to 90 fps. What in the world is going on? [displayLink setPreferredFrameRateRange:CAFrameRateRangeMake(100,240,120)]; //Min. Max, Preferred [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; (Also, CADisableMinimumFrameDurationOnPhone is set to True in info.plist)
2
0
545
Oct ’24
In iOS 18 beta, UICollectionView/scrollToItem(at:at:animated:) now scrolls at 4 FPS
In iOS 18.0, UICollectionView/scrollToItem(at:at:animated:) causes UICollectionView to scroll with an awkward stuttered animation at about 4 frames per second on the device. This behavior is not observed in iOS 15, 16, or 17. I submitted FB13986989 about this issue 6 weeks ago when I first observed it in iOS 18 beta 1, but I have not received a response. The issue has not been fixed in iOS 18.0 beta 2, 3, or 4. Is this a known issue? Does Apple intend to fix it? Assuming Apple has not prioritized fixing this bug, could a UIKit engineer suggest a workaround? My app relies on scrollTo working properly and now looks awful on iOS 18. Thank you for any assistance you can provide.
2
0
677
Aug ’24
Fold animation in UICollectionViewLayout
I want to achieve Fold animation when the user scrolls UICollectionView. I have UICollectionView with full-screen size cell and vertically scrolling with paging enabled. For that I've created sub-class of UICollectionViewFlowLayout which is as described below. class FoldingFlowLayout: UICollectionViewFlowLayout { private let logger = Logger(subsystem: bundleIdentifier, category: "FlowLayout") override func prepare() { super.prepare() scrollDirection = .vertical minimumLineSpacing = 0 minimumInteritemSpacing = 0 } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) attributes?.forEach { attribute in transformLayoutAttributes(attribute) } return attributes } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } private func transformLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) { guard let collectionView = collectionView else { return } let contentOffsetY = collectionView.contentOffset.y let cellOffsetY = attributes.frame.origin.y - contentOffsetY let cellHeight = attributes.frame.height var transform = CATransform3DIdentity transform.m34 = -1.0 / 500.0 // Apply perspective if cellOffsetY < cellHeight && cellOffsetY > -cellHeight { let angle = (cellOffsetY / cellHeight) * .pi / 2 transform = CATransform3DRotate(transform, angle, -1, 0, 0) attributes.transform3D = transform attributes.alpha = 1.0 - (abs(cellOffsetY) / cellHeight) } else { attributes.transform3D = CATransform3DIdentity attributes.alpha = 1.0 } } } But this is not working as I expected. I want to create replica of this kind of animation. What am I missing here?
0
0
311
Jul ’24
How do I stop Tasks from choking up animations?
I'm making a loading screen, but I can't figure out how to make the loading indicator animate smoothly while work is being performed. I've tried a variety of tactics, including creating confining the animation to a .userInitiated Task, or downgrading the loading Task to .background, and using TaskGroups. All of these resulted in hangs, freezes, or incredibly long load times. I've noticed that standard ProgressViews work fine when under load, but the documentation doesn't indicate why this is the case. Customized ProgressViews don't share this trait (via .progressViewStyle()) also choke up. Finding out why might solve half the problem. Note: I want to avoid async complications that come with using nonisolated functions. I've used them elsewhere, but this isn't the place for them.
9
0
640
Aug ’24
Fold animation in UICollectionViewLayout
I want to achieve Fold animation when the user scrolls UICollectionView. I have UICollectionView with full-screen size cell and vertically scrolling with paging enabled. For that I've created sub-class of UICollectionViewFlowLayout which is as described below. class FoldingFlowLayout: UICollectionViewFlowLayout { private let logger = Logger(subsystem: bundleIdentifier, category: "FlowLayout") override func prepare() { super.prepare() scrollDirection = .vertical minimumLineSpacing = 0 minimumInteritemSpacing = 0 } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) attributes?.forEach { attribute in transformLayoutAttributes(attribute) } return attributes } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } private func transformLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) { guard let collectionView = collectionView else { return } let contentOffsetY = collectionView.contentOffset.y let cellOffsetY = attributes.frame.origin.y - contentOffsetY let cellHeight = attributes.frame.height var transform = CATransform3DIdentity transform.m34 = -1.0 / 500.0 // Apply perspective if cellOffsetY < cellHeight && cellOffsetY > -cellHeight { let angle = (cellOffsetY / cellHeight) * .pi / 2 transform = CATransform3DRotate(transform, angle, -1, 0, 0) attributes.transform3D = transform attributes.alpha = 1.0 - (abs(cellOffsetY) / cellHeight) } else { attributes.transform3D = CATransform3DIdentity attributes.alpha = 1.0 } } } But this is not working as I expected. I want to create replica of this kind of animation. What am I missing here?
1
0
453
Aug ’24
Modifying the view hierarchy of a UITableViewCell as part of reloadData
I am trying to debug a crash due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSFrozenDictionary layoutSublayers]: unrecognized selector sent to instance. I see 2 things that I find interesting about it. The fact that the instance is a __NSFrozenDictionary tells me that the reference to a CALayer that has since been evicted from memory and re-written. The call to layoutSublayers tells me that the CALayer was dealloc-ed at some point between the call to setNeedsLayout (or layoutIfNeeded) This seemingly occurs as part of a call to -[UITableView reloadData] Furthermore, each cell created by the UITableView has a UIStackView. As part of the call to cellForRowAtIndexPath the code adds an instance of "custom view" to the stack view's subviews. As part of the call to prepareForReuse the code removes the "custom view" from the stack view's subviews. Therefore as part of the prepareForReuse the "custom view" (and its layer) is evicted from memory. My theory is that the tableview does a layout pass on the visible cell which has since had its subview removed which causes the crash. My question is what are the constraints on when/where to call reloadData and/or when/where you should definitely avoid it as it relates to this context? This is code that modifies the view hierarchy of the cell as part of its lifecycle which AFAIK this is "not supported" since prepareForReuse is meant to be used for resetting view state and cellForRowAtIndexPath to reset content. In that sense, another question, are you not allowed to modify the cell view hierarchy period as part of the cycle to draw the visible cells or is it more of a case, do not call reloadData?
2
0
481
Jul ’24