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..
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)!
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With