Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API for playing multiple markers sequentially #2084

Merged
merged 2 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Example/iOS/ViewControllers/AnimationPreviewViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ class AnimationPreviewViewController: UIViewController {
updateAnimation()
}),
]),

UIAction(
title: "Play Markers...",
handler: { [unowned self] _ in
displayMarkerList()
}),
]))
}

Expand All @@ -248,4 +254,36 @@ class AnimationPreviewViewController: UIViewController {
animationView.animationSpeed = speed
configureSettingsMenu()
}

private func displayMarkerList() {
let markersList: String
let markerNames = animationView.animation?.markerNames ?? []
if markerNames.isEmpty {
markersList = "No markers included in animation"
} else {
markersList = markerNames.joined(separator: ", ")
}

let alert = UIAlertController(title: "Markers", message: markersList, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))

alert.addTextField { textField in
textField.placeholder = "Comma separated list of markers to play in order"
}

alert.addAction(UIAlertAction(title: "Play", style: .default) { [weak alert, weak self] _ in
guard
let self = self,
let textInput = alert?.textFields?.first?.text,
!textInput.isEmpty
else { return }

let markersToPlay = textInput.components(separatedBy: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }

self.animationView.play(markers: markersToPlay)
})
present(alert, animated: true)
}

}
36 changes: 36 additions & 0 deletions Sources/Public/Animation/LottieAnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,42 @@ public class LottieAnimationLayer: CALayer {
completion: completion)
}

/// Plays the given markers sequentially in order.
///
/// A marker is a point in time with an associated duration that is encoded into the
/// animation data and assigned a name. Multiple markers can be played sequentially
/// to create programmable animations.
///
/// If a marker is not found, it will be skipped.
///
/// If a marker doesn't have a duration value, it will play with a duration of 0
/// (effectively being skipped).
///
/// If another animation is played (by calling any `play` method) while this
/// marker sequence is playing, the marker sequence will be cancelled.
///
/// - Parameter markers: The list of markers to play sequentially.
open func play(markers: [String]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to worry about the case where this could get called again even while the current sequence is still going? I would imagine if we called this again with a new set of markers, we should stop playing the previous set and start fresh with the new set

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thinking. I checked and this is handled automatically by the current implementation:

  1. when an animation is cancelled because another animation is started, the completion handler of the cancelled animation is called with success: false
  2. we only continue to the next marker segment when success is true
    I'll add some comments to mention this behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great! thanks for verifying the behavior here

guard !markers.isEmpty else { return }

let markerToPlay = markers[0]
let followingMarkers = Array(markers.dropFirst())

guard animation?.markerMap?[markerToPlay] != nil else {
play(markers: followingMarkers)
return
}

play(marker: markerToPlay, loopMode: .playOnce, completion: { [weak self] success in
// If the completion handler is called with `success: false` (which typically means
// that another animation was played by calling some `play` method),
// we should cancel the marker sequence and not play the next marker.
guard success, let self = self else { return }

self.play(markers: followingMarkers)
})
}

/// Stops the animation and resets the layer to its start frame.
///
/// The completion closure will be called with `false`
Expand Down
19 changes: 19 additions & 0 deletions Sources/Public/Animation/LottieAnimationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,25 @@ open class LottieAnimationView: LottieAnimationViewBase {
lottieAnimationLayer.play(marker: marker, loopMode: loopMode, completion: completion)
}

/// Plays the given markers sequentially in order.
///
/// A marker is a point in time with an associated duration that is encoded into the
/// animation data and assigned a name. Multiple markers can be played sequentially
/// to create programmable animations.
///
/// If a marker is not found, it will be skipped.
///
/// If a marker doesn't have a duration value, it will play with a duration of 0
/// (effectively being skipped).
///
/// If another animation is played (by calling any `play` method) while this
/// marker sequence is playing, the marker sequence will be cancelled.
///
/// - Parameter markers: The list of markers to play sequentially.
open func play(markers: [String]) {
lottieAnimationLayer.play(markers: markers)
}

/// Stops the animation and resets the view to its start frame.
///
/// The completion closure will be called with `false`
Expand Down
2 changes: 1 addition & 1 deletion Tests/Samples/Issues/issue_1915.json

Large diffs are not rendered by default.