如何通过滑块控制Xcode中SCNAction编辑器创建的动画进度
Hey there! I totally get the frustration with SCNAction's lack of built-in seek functionality—those actions are perfect for straightforward playback, but they really fall short when you need precise scrubbing control like a slider. Let's walk through two solid workarounds to get this done:
Option 1: Switch to CAAnimation (Recommended)
Core Animation (CAAnimation) has native support for time scrubbing, thanks to properties like timeOffset and speed. This is the easiest approach if you can refactor your animation to use CA instead of SCNAction.
Step-by-Step Implementation:
Convert your SCNAction to a CAAnimation:
For example, if you had a move action, create aCABasicAnimationtargeting the node's position:let moveAnimation = CABasicAnimation(keyPath: "position") moveAnimation.fromValue = SCNVector3(0, 0, 0) moveAnimation.toValue = SCNVector3(10, 0, 0) moveAnimation.duration = 2.0 // Match your original SCNAction's duration moveAnimation.fillMode = .forwards moveAnimation.isRemovedOnCompletion = false // Add the animation to your node yourSCNNode.addAnimation(moveAnimation, forKey: "scrubbableMove")Link your slider to control the animation:
When the slider's value changes, pause the animation and adjust itstimeOffsetto jump directly to the desired point:@IBAction func sliderValueChanged(_ sender: UISlider) { let progress = sender.value let totalDuration = 2.0 // Match your animation's duration // Pause the node to freeze playback while scrubbing yourSCNNode.pause() // Update the animation's time offset to match the slider's progress if let animation = yourSCNNode.animation(forKey: "scrubbableMove") { animation.timeOffset = totalDuration * Double(progress) } } // Optional: Resume playback when the user releases the slider @IBAction func sliderTouchUp(_ sender: UISlider) { yourSCNNode.resume() }
Option 2: Manually Manage Keyframes with SCNAction
If you absolutely need to stick with SCNAction, you'll have to precompute keyframes for your animation and manually set the node's properties when the slider moves. This is more work but doable.
Step-by-Step Implementation:
Predefine your animation keyframes:
Create a struct to store each keyframe's time and corresponding node properties:struct AnimationKeyframe { let time: Double let position: SCNVector3 // Add other properties you're animating (rotation, scale, etc.) } // Populate with your animation's key points let moveKeyframes: [AnimationKeyframe] = [ AnimationKeyframe(time: 0, position: SCNVector3(0, 0, 0)), AnimationKeyframe(time: 1, position: SCNVector3(5, 0, 0)), AnimationKeyframe(time: 2, position: SCNVector3(10, 0, 0)) ]Run your original SCNAction (but control it manually):
let originalMoveAction = SCNAction.move(to: SCNVector3(10, 0, 0), duration: 2.0) yourSCNNode.runAction(originalMoveAction, forKey: "originalMove")Handle slider changes to jump to keyframes:
When the slider moves, pause the SCNAction, then interpolate between keyframes to set the node's current state:@IBAction func sliderValueChanged(_ sender: UISlider) { let targetTime = Double(sender.value) * 2.0 // Total animation duration // Pause the original action to stop automatic playback yourSCNNode.pauseAnimation(forKey: "originalMove") guard let firstFrame = moveKeyframes.first, let lastFrame = moveKeyframes.last else { return } // Jump to start or end if slider is at extremes if targetTime <= firstFrame.time { yourSCNNode.position = firstFrame.position return } if targetTime >= lastFrame.time { yourSCNNode.position = lastFrame.position return } // Find the keyframes surrounding the target time let precedingFrame = moveKeyframes.last { $0.time <= targetTime }! let followingFrame = moveKeyframes.first { $0.time >= targetTime }! // Calculate progress between the two keyframes let timeGap = followingFrame.time - precedingFrame.time let progress = (targetTime - precedingFrame.time) / timeGap // Interpolate the position (repeat for other animated properties) let interpolatedPosition = SCNVector3( precedingFrame.position.x + (followingFrame.position.x - precedingFrame.position.x) * progress, precedingFrame.position.y + (followingFrame.position.y - precedingFrame.position.y) * progress, precedingFrame.position.z + (followingFrame.position.z - precedingFrame.position.z) * progress ) yourSCNNode.position = interpolatedPosition }
Key Notes:
- CAAnimation is preferred because it handles interpolation and timing curves automatically, saving you from writing custom interpolation logic.
- When using SCNAction with keyframes, make sure your keyframes are dense enough to make scrubbing feel smooth.
- Remember to resume animations (either the node or specific action) when the user finishes scrubbing if you want playback to continue.
内容的提问来源于stack exchange,提问作者Pineapple3D

