Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: animate changes that depend on @ObjectBinding

SwiftUI has implicit animations with .animate(), and explicit ones using .withAnimation(). However, I can't figure out how to animate an image change:

struct ImageViewWidget : View {
  @ObjectBinding var imageLoader: ImageLoader

  init(imageURL: URL) {
    imageLoader = ImageLoader(imageURL: imageURL)
  }

  var body: some View {
    Image(uiImage:
      (imageLoader.data.count == 0) ? UIImage(named: "logo-old")! :  UIImage(data: imageLoader.data)!)
        .resizable()
        .cornerRadius(5)
        .frame(width: 120, height:120)
  }
}

This Image's uiImage argument is passed the old-logo (placeholder) if there's no data in imageLoader (a BindableObject), and replaces it with the correct one once that's asynchronously loaded:

class ImageLoader : BindableObject {
  let didChange = PassthroughSubject<Data, Never>()

  var data = Data() {
    didSet {
      didChange.send(data)
    }
  }

  init(imageURL: URL) {
    print("Image loader being initted!")
    let url = imageURL

    URLSession.shared.dataTask(with: url) { (data, _, _) in
      guard let data = data else { return }
      DispatchQueue.main.async {
        self.data = data
      }
      }.resume()

  }
}

How can I animate this change, the moment where data.count stops being 0, and we have the image? say I want a fade out-in animation..

like image 310
zerohedge Avatar asked Jun 15 '19 19:06

zerohedge


1 Answers

If you want to use explicit animations based on environment objects (or observable objects), you need to create some state in your view.

You can react to changes to an observable in your view using onReceive, and then modify your state using explicit animation.

struct ImageViewWidget: View {
    @ObservedObject var imageLoader: ImageLoader
    @State var uiImage: UIImage = UIImage(named: "logo-old")!

    init(imageURL: URL) {
        imageLoader = ImageLoader(imageURL: imageURL)
    }

    var body: some View {
        Image(uiImage: uiImage)
            .resizable()
            .cornerRadius(5)
            .frame(width: 120, height: 120)
            .onReceive(imageLoader.$data) { data in
                if data.count != 0 {
                    withAnimation {
                        self.uiImage = UIImage(data: data)!
                    }
                }
            }
    }
}
like image 79
Chris Pearce Avatar answered Oct 20 '22 10:10

Chris Pearce