Issue #448
Use ObservableObject
and onReceive
to receive event. URLSession.dataTask
reports in background queue, so need to .receive(on: RunLoop.main)
to receive events on main queue.
For better dependency injection, need to use ImageLoader
from Environment
There should be a way to propagate event from Publisher
to another Publisher
, for now we use sink
ImageLoader.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Combineimport WatchKitclass ImageLoader : ObservableObject { private var cancellable: AnyCancellable? let objectWillChange = PassthroughSubject <UIImage? , Never >() func load (url: URL) { self .cancellable = URLSession .shared .dataTaskPublisher(for : url) .map ({ $0 .data }) .eraseToAnyPublisher() .receive(on: RunLoop .main) .map ({ UIImage (data: $0 ) }) .replaceError(with: nil ) .sink(receiveValue: { image in self .objectWillChange.send(image) }) } func cancel () { cancellable?.cancel() } }
RemoteImage.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import SwiftUIimport WatchKitstruct RemoteImage : View { let url: URL let imageLoader = ImageLoader () @State var image: UIImage? = nil var body: some View { Group { makeContent() } .onReceive(imageLoader.objectWillChange, perform: { image in self .image = image }) .onAppear(perform: { self .imageLoader.load(url: self .url) }) .onDisappear(perform: { self .imageLoader.cancel() }) } private func makeContent () -> some View { if let image = image { return AnyView ( Image (uiImage: image) .resizable() .aspectRatio(contentMode: .fit) ) } else { return AnyView (Text ("😢" )) } } }