Skip to content

Commit 267f424

Browse files
committed
Fix plane animation bug.
1 parent 1a89a32 commit 267f424

File tree

2 files changed

+55
-55
lines changed

2 files changed

+55
-55
lines changed

Shared/Samples/Animate 3D graphic/Animate3DGraphicView.Model.swift

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,15 @@ extension Animate3DGraphicView {
128128
@Published private(set) var cameraPropertyTexts: [CameraProperty: String] = [:]
129129

130130
init() {
131-
// Set up the mission and the graphics.
131+
// Set up the mission, graphics, and animation.
132132
updateMission()
133+
134+
let displayLink = CADisplayLink(target: self, selector: #selector(updatePositions))
135+
animation.setup(displayLink: displayLink)
136+
}
137+
138+
deinit {
139+
Task { await animation.displayLink?.invalidate() }
133140
}
134141

135142
// MARK: Methods
@@ -155,24 +162,6 @@ extension Animate3DGraphicView {
155162
}
156163
}
157164

158-
/// Starts a new animation by creating a timer used to move the graphics.
159-
func startAnimation() {
160-
// Stop any previous on going animation.
161-
animation.stop()
162-
animation.isPlaying = true
163-
164-
// Create a new timer to update the graphics' position each iteration.
165-
let interval = 1 / Double(animation.speed)
166-
animation.timer = Timer.scheduledTimer(
167-
timeInterval: interval,
168-
target: self,
169-
selector: #selector(updatePositions),
170-
userInfo: nil,
171-
repeats: true
172-
)
173-
RunLoop.current.add(animation.timer!, forMode: .common)
174-
}
175-
176165
/// Updates the text associated with a given camera controller property.
177166
/// - Parameters:
178167
/// - property: The camera controller property associated with the text to update.
@@ -223,14 +212,18 @@ extension Animate3DGraphicView {
223212

224213
/// A struct containing data for an animation.
225214
struct Animation {
226-
/// The timer for the animation used to loop through the animation frames.
227-
var timer: Timer?
215+
/// The timer used to loop through the animation frames.
216+
private(set) var displayLink: CADisplayLink?
228217

229-
/// The speed of the animation used to set the timer's time interval.
230-
var speed = 50.0
218+
/// The speed of the animation.
219+
var speed: AnimationSpeed = .medium
231220

232-
/// A Boolean that indicates whether the animation is currently playing.
233-
var isPlaying = false
221+
/// A Boolean value indicating whether the animation is currently playing.
222+
var isPlaying = false {
223+
didSet {
224+
displayLink?.isPaused = !isPlaying
225+
}
226+
}
234227

235228
/// The current frame of the animation.
236229
var currentFrame: Frame {
@@ -255,26 +248,31 @@ extension Animate3DGraphicView {
255248
/// The index of the current frame in the frames list.
256249
private var currentFrameIndex = 0
257250

258-
/// Stops the animation by invalidating the timer.
259-
mutating func stop() {
260-
timer?.invalidate()
261-
isPlaying = false
251+
/// Sets up the animation using a given display link.
252+
/// - Parameter displayLink: The display link used to run the animation.
253+
mutating func setup(displayLink: CADisplayLink) {
254+
// Add the display link to main thread common mode run loop,
255+
// so it is not effected by UI events.
256+
displayLink.add(to: .main, forMode: .common)
257+
displayLink.preferredFramesPerSecond = 60
258+
self.displayLink = displayLink
262259
}
263260

264261
/// Resets the animation to the beginning.
265262
mutating func reset() {
266-
stop()
263+
isPlaying = false
267264
currentFrameIndex = 0
268265
}
269266

270-
/// Increments the animation to the next frame.
267+
/// Increments the animation to the next frame based on the speed.
271268
mutating func nextFrame() {
272-
if currentFrameIndex >= framesCount - 1 {
269+
// Increment the frame index using the current speed.
270+
let nextFrameIndex = currentFrameIndex + speed.rawValue
271+
if frames.indices.contains(nextFrameIndex) {
272+
currentFrameIndex = nextFrameIndex
273+
} else {
273274
// Reset the animation when it has reached the end.
274275
reset()
275-
} else {
276-
// Move the index to point to the next frame.
277-
currentFrameIndex += 1
278276
}
279277
}
280278

@@ -359,6 +357,13 @@ extension Animate3DGraphicView {
359357
}
360358
}
361359
}
360+
361+
/// An enumeration representing the speed of the animation.
362+
enum AnimationSpeed: Int, CaseIterable {
363+
case slow = 1
364+
case medium = 2
365+
case fast = 4
366+
}
362367
}
363368

364369
private extension FormatStyle where Self == FloatingPointFormatStyle<Double> {

Shared/Samples/Animate 3D graphic/Animate3DGraphicView.swift

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct Animate3DGraphicView: View {
4242
StatRow("Roll", value: model.animation.currentFrame.roll.formatted(.angle))
4343
}
4444
.frame(width: 170, height: 100)
45+
.padding([.leading, .trailing])
4546
.background(.ultraThinMaterial)
4647
.cornerRadius(10)
4748
.shadow(radius: 3)
@@ -81,7 +82,7 @@ struct Animate3DGraphicView: View {
8182

8283
/// The play/pause button for the animation.
8384
Button {
84-
model.animation.isPlaying ? model.animation.stop() : model.startAnimation()
85+
model.animation.isPlaying.toggle()
8586
} label: {
8687
Image(systemName: model.animation.isPlaying ? "pause.fill" : "play.fill")
8788
}
@@ -95,15 +96,18 @@ struct Animate3DGraphicView: View {
9596
.task {
9697
await model.monitorCameraController()
9798
}
98-
.onDisappear {
99-
model.animation.stop()
100-
}
10199
}
102100

103101
/// The list containing the mission settings.
104102
private var missionSettings: some View {
105103
List {
106104
Section("Mission") {
105+
VStack {
106+
StatRow("Progress", value: model.animation.progress.formatted(.rounded))
107+
ProgressView(value: model.animation.progress)
108+
.padding(.bottom)
109+
}
110+
107111
Picker("Mission Selection", selection: $model.currentMission) {
108112
ForEach(Mission.allCases, id: \.self) { mission in
109113
Text(mission.label)
@@ -113,22 +117,14 @@ struct Animate3DGraphicView: View {
113117
.labelsHidden()
114118
}
115119

116-
Section {
117-
VStack {
118-
StatRow("Animation Speed", value: model.animation.speed.formatted())
119-
Slider(value: $model.animation.speed, in: 1...200, step: 1)
120-
.onChange(of: model.animation.speed) { _ in
121-
if model.animation.isPlaying {
122-
model.startAnimation()
123-
}
124-
}
125-
.padding(.horizontal)
126-
}
127-
VStack {
128-
StatRow("Mission Progress", value: model.animation.progress.formatted(.rounded))
129-
ProgressView(value: model.animation.progress)
130-
.padding()
120+
Section("Speed") {
121+
Picker("Animation Speed", selection: $model.animation.speed) {
122+
ForEach(AnimationSpeed.allCases, id: \.self) { speed in
123+
Text(String(describing: speed).capitalized)
124+
}
131125
}
126+
.pickerStyle(.inline)
127+
.labelsHidden()
132128
}
133129
}
134130
}
@@ -185,7 +181,6 @@ extension Animate3DGraphicView {
185181
Spacer()
186182
Text(value)
187183
}
188-
.padding([.leading, .trailing])
189184
}
190185
}
191186
}

0 commit comments

Comments
 (0)