How to fetch an image in the Live Activity (ActivityKit)

Is it possible to load an image from remote in the Live Activity using ActivityKit? I tried various different methods, but none of them are working including:

  • AsyncImage
  • Pre-fetching the image in the App and passing that image to Activity as a Data through the context when starting the activity and converting Data back to the Image
Post not yet marked as solved Up vote post of kvarantz Down vote post of kvarantz
6.1k views
  • Your method works, thanks you very much

Add a Comment

Replies

As far as I can tell, the Live Activity itself is not capable of performing networking tasks and, thus, AsyncImage is not likely to be suitable. As the Live Activity is more aligned with how a Widget works, taking the approach of how images are shared between a host/parent app and the Widget Extension seems to be a viable way to display images on the Lock Screen/Dynamic Island within ActivityKit.

Data can be shared between a host/parent app and a Widget Extension by taking advantage of App Groups (you can find this under the Signing & Capabilities tab of your project's settings in Xcode). In your host/parent target, you can enable App Groups, and choose either one of your existing App Groups (or create a new App Group). You can do the same for your Widget Extension target, as well, hereby allowing your host/parent app and the Widget Extension to have a shared App Group repository that can share data.

Then, in your host/parent app, you can add in or modify an existing method that pre-fetches an image. For example, something like;

    private func downloadImage(from url: URL) async throws -> URL? {
        guard var destination = FileManager.default.containerURL(
            forSecurityApplicationGroupIdentifier: "group.com.example.liveactivityappgroup")
        else { return nil }

        destination = destination.appendingPathComponent(url.lastPathComponent)        

        guard !FileManager.default.fileExists(atPath: destination.path()) else {
            print("No need to download \(url.lastPathComponent) as it already exists.")
            return destination
        }        

        let (source, _) = try await URLSession.shared.download(from: url)
        try FileManager.default.moveItem(at: source, to: destination)
        print("Done downloading \(url.lastPathComponent)!")
        return destination
    }

In my example here, I am taking in a URL, say of an image on the internet, checking if my "App Group" container exists for this target, appending the file name to the "App Group" container's FileManager URL, and either returning that URL, if the image already has been downloaded, or downloading the image.

Once you have your image downloaded and stored in the "App Group" container, your Widget Extension/Live Activity could take advantage of this. For example, in your ActivityAttributes (or ActivityAttributes.ContentState) that you have defined for your Live Activity, you could have a var imageName: String that gets set when you create/update your Live Activity. Since this would then be passed via the context to the Widget Extension, you could do the reverse in your Widget Extension/Live Activity and render the image.

For example, in your Widget Extension/Live Activity's code, you could add something like;

if let imageContainer = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: "group.com.example.liveactivityappgroup")?
    .appendingPathComponent(context.state.imageName),
   let uiImage = UIImage(contentsOfFile: imageContainer.path()) {
    Image(uiImage: uiImage)
        .resizable()
        .aspectRatio(contentMode: .fill)
        .frame(width: 50, height: 50)
}

This checks that the Widget Extension can access the App Group repository, appends the image name, as passed in the context, creates a UIImage, and if successful, then renders the UIImage using SwiftUI's Image.

  • Thanks for the detailed answer

  • Hi, I tried it, but it didn't work.

  • Hi, Can you share the complete code? I'm flutter developer, I do not understand that code

Hi, I was doing something similar using AppStorage and AppGroup to pass the fetched image data from the main app to the Live Activity and Dynamic Island. It previously worked in iOS 16.1 beta 1, and beta 3 but hasn't resulted in rendering the image for the last two betas as well as the RC. I tried the above solution as well, and the data is correctly fetched and passed, but the Live Activity will not render Image(uiImage: UIImage(data: data)). It will render any SF Symbol or static image in the asset catalogue just fine, but not usable otheriwse.

Can someone help determine a way to render a fetched image in a Live Activity or Dynamic Island? Is there a trick to getting Image(uiImage: UIImage(data: data)) to render properly?

  • Have you found a workaround?

Add a Comment

Hi, same issue as @Oghweb, can't figure to display an image using Data in live activities / dynamic island. Can anyone has a solution ? Thanks

Hello, even I tried displaying an image by adding it to the newly added Widget/Assets, and trying to access it using -> Image("image_name"). This doesn't even trigger Dynamic island. The systemImage works just perfect. Is that a ActivityKit limitation?

I have seen lyft use image on leading region, not sure how they got it up. Any pointers?

Add a Comment

When storing in an app group as explained by BrandonK212, https://developer.apple.com/forums/thread/716902?answerId=731691022#731691022, it seems like using a combination of Data(contentsOf:) with a file URL and UIImage(data:) worked for me, where UIImage(contentsOfFile:) did not.

Related thread that suggests Apple recommends the app group approach at least: https://developer.apple.com/forums/thread/715170

  • Still not working for me. Can you show me the sample code?

  • Hey, I tried your approach but it crashes at Image(uiImage: uiImage). It correctly gets the UIImage from App Group, and was able to confirm this through showing raw data of the uiImage through Text. Do you know why this happens?

  • @tkerridge did it work in the dynamic island? I can get images loading from userdefaults as data on the lock screen but not in the dynamic island.

For anyone experiencing the issue where the image is showing up on the Lock Screen, but adding it to the island blows up the whole activity, I figured this out. There was the following log in Console.app:

fault 13:19:22.889771-0500 WidgetExtension Widget archival failed due to image being too large [22] - (320, 320).

Resizing the UIImage before passing to Image fixed the problem. I just guessed and went for 84px wide.

This error should really show up in Xcode itself and the required size should be specified in the documentation (I just guessed at 84px which seems to work). Additionally, .frame() on the SwiftUI Image type should be able to accomplish the same thing.

Followed the example from @brandonK212. Works great. However, when I update the activity the image seems to fade in and out. Like its re-rendering. Is there any way, I can fix this? New to swift.

  • I have encountered the same problem - though I am accessing the image from the widget assets folder. Have you found a solution that fixed the fade in/out?

Add a Comment

For everyone the code of @brandonK212 work fine, but in the image with extention jpg dont work so check the image dont contains jpg extesion

During development we also found that the Dynamic Island has a restriction on the memory use and large images were producing the error above.

I was able to get it working using @brandonK212 approach but here's the thing, what is the "proper" way of loading a file into image from the ActivityConfiguration?

Right now I have

            let _ = print(context.attributes.imageFilename)
            if let image = FileIO.load(filename: context.attributes.imageFilename) {
                Image(uiImage: image)
            }
        } 

But is that even a good idea? It looks like I have to do file IO every time the view is rendered. I don't think you can hold State or StateObjects because this isn't a SwiftUI View

Has anyone solved this problem yet? I have issue about url image show in simulator work, but device not working.

NetworkImage(url: URL(string: context.attributes.imageGame))
                  .aspectRatio(contentMode: .fill)
                  .frame(width: 35, height: 35)
                  .clipShape(Circle())
struct NetworkImage: View {
  let url: URL?
  
  var body: some View {
    Group {
      if let url = url, let imageData = try? Data(contentsOf: url),
         let uiImage = UIImage(data: imageData) {
        
        Image(uiImage: uiImage)
          .resizable()
      }
      else {
        Image("league")
      }
    }
  }
}