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

Synchronize animated image playback inside of LazyStack #300

Open
LosFarmosCTL opened this issue Mar 3, 2024 · 7 comments
Open

Synchronize animated image playback inside of LazyStack #300

LosFarmosCTL opened this issue Mar 3, 2024 · 7 comments
Labels
good first issue Good for newcomers

Comments

@LosFarmosCTL
Copy link

The application I am working on needs to display a lot of animated "emotes" within text messages, often multiple occurrences of the same image, which get out of sync when the individual messages are being scrolled inside of a lazy stack. Since the playback is paused when they move out of view and resumed at the old position when they reappear, the other images have moved on to different frames in the meantime.

Now, I feel like the synchronization aspect should probably be on me (the actual application) to handle, but I can't figure out any way to control the playback of animated images. The image player is not exposed, neither are there methods to control it indirectly.

Would be great to have an option for that, since otherwise the only way I see for me to move forward is building my own custom wrapper for SDWebImage that enables me to do so, which would be a shame compared to just using this more matured library.

import SwiftUI
import SDWebImageSwiftUI

struct ContentView: View {
  let imageURL = URL(string: "https://cdn.betterttv.net/emote/5a8314b61686393232d31027/3x.gif")!

  var body: some View {
    ScrollView {
      LazyVStack {
        ForEach(1...200, id: \.self) { _ in
          AnimatedImage(url: imageURL)
            .resizable()
            .frame(width: 25, height: 25)
        }
      }
    }
  }
}
RPReplay_Final1709503396.MP4
@LosFarmosCTL
Copy link
Author

PS: Things I have tested so far:

  • use WebImage, but even if using it would work, the performance is absolutely unfeasible for my use case, there might be 100+ animated images displayed at once, which is fine using the UIKit implementation but just doesn't scale at all for SwiftUI
  • create the AnimatedImage instance only once as a constant and use that inside of body, doesn't change anything and is definitely not how SwiftUI should work
  • look at the available constructor options and methods of AnimatedImage

@dreampiggy
Copy link
Collaborator

dreampiggy commented Mar 18, 2024

This is a feature which need the upstream SDWebImage support

In SD6, we will support all of playing status on each image view in sync

Currently seems hard to do, until we use a shared SDAnimatedImagePlayer, which need break changes. (Currently, each AnimatedImage hold a different SDAnimatedImagePlayer internally, sync the status between them is hard)

@dreampiggy dreampiggy added the good first issue Good for newcomers label Mar 18, 2024
@LosFarmosCTL
Copy link
Author

In SD6, we will support all of playing status on each image view in sync

Great to hear that this is planned! Is there any rough timeframe for when we could expect SD6?

Depending on that I'll have to decide if I'd just wait for the update or if implementing synchronization in my App using SDAnimatedImagePlayer directly myself is worth it.

@dreampiggy
Copy link
Collaborator

dreampiggy commented May 6, 2024

Seems long time pass.

Do you still have this feature request ?

I'd like to introdce a API, which allows you to sync different image views (For UIKit, it's SDAnimatedImageView, for SwiftUI, it's AnimatedImage).

Just like this (example API usage)

  @State isPlaying: Bool = true
  var player: AnimatedImagePlayer(url: imageURL, isPlaying: $isPlaying)
  var body: some View {
    ScrollView {
      LazyVStack {
        ForEach(1...200, id: \.self) { _ in
          AnimatedImage(player: player)
            .resizable()
            .frame(width: 25, height: 25)
        }
      }
    }

@LosFarmosCTL
Copy link
Author

Do you still have this feature request ?

I'd like to introdce a API, which allows you to sync different image views (For UIKit, it's SDAnimatedImageView, for SwiftUI, it's AnimatedImage).

Just like this (example API usage)

Yes, that would be exactly what I am looking for!

@Kyle-Ye
Copy link

Kyle-Ye commented May 13, 2024

Seems long time pass.

Do you still have this feature request ?

I'd like to introdce a API, which allows you to sync different image views (For UIKit, it's SDAnimatedImageView, for SwiftUI, it's AnimatedImage).

Just like this (example API usage)

  @State isPlaying: Bool = true
  var player: AnimatedImagePlayer(url: imageURL, isPlaying: $isPlaying)
  var body: some View {
    ScrollView {
      LazyVStack {
        ForEach(1...200, id: \.self) { _ in
          AnimatedImage(player: player)
            .resizable()
            .frame(width: 25, height: 25)
        }
      }
    }

DM reply and post the code here cc @dreampiggy

We can use something like the tag and coordinateSpace API

Public API

public struct AnimatedImageCoordinateTag: EnvironmentKey {
    public static var defaultValue: String? { nil }
}

extension EnvironmentValues {
    public var animatedImageCoordinateTag: String? {
        get { self[AnimatedImageCoordinateTag.self] }
        set { self[AnimatedImageCoordinateTag.self] = newValue }
    }
}

Downstream Usage

struct TestContentView: View {
    let imageURL = URL(string: "https://cdn.betterttv.net/emote/5a8314b61686393232d31027/3x.gif")!

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(1 ... 200, id: \.self) { _ in
                    AnimatedImage(url: imageURL)
                        .resizable()
                        .frame(width: 25, height: 25)
                        .environment(\.animatedImageCoordinateTag, "A")
                }
                ForEach(1 ... 200, id: \.self) { _ in
                    AnimatedImage(url: imageURL)
                        .resizable()
                        .frame(width: 25, height: 25)
                        .environment(\.animatedImageCoordinateTag, "B")
                }
            }
        }
    }
}

Internal implementation

public struct AnimatedImage : PlatformViewRepresentable {
    ...
    public func makeUIView(context: Context) -> AnimatedImageViewWrapper {
        let tag = context.environment.animatedImageCoordinateTag
        // Framework internal logic
        ...
    }
    
    public func updateUIView(_ uiView: AnimatedImageViewWrapper, context: Context) {
        let tag = context.environment.animatedImageCoordinateTag
        // Framework internal logic
        ...
    }
}

If you have more question about EnvironmentKey or EnvironmentValues
You can give my implementation of EnvironmentValues and EnvironmentKey a look

@dreampiggy
Copy link
Collaborator

Is this behavior what you want ?

Demo.mov.zip

Note for the Synchronization, we have some hack behavior like pauseable modifier, which may cause other view status mass. Currently I disable that support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

3 participants