diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index f35381f..0000000 --- a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,32 +0,0 @@ -{ - "pins" : [ - { - "identity" : "nuke", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Nuke.git", - "state" : { - "revision" : "c3864b8882bc69f5edfe5c70e18786c91d228b28", - "version" : "12.1.3" - } - }, - { - "identity" : "swiftui-gesture-velocity", - "kind" : "remoteSourceControl", - "location" : "https://github.com/FluidGroup/swiftui-gesture-velocity", - "state" : { - "revision" : "9c83f8995f9e5efc29db2fca4b9ff058283f1603", - "version" : "1.0.0" - } - }, - { - "identity" : "swiftui-support", - "kind" : "remoteSourceControl", - "location" : "https://github.com/FluidGroup/swiftui-support.git", - "state" : { - "revision" : "8ef53190c33bd345e7a95ef504dafe0f85ad9c4d", - "version" : "0.4.1" - } - } - ], - "version" : 2 -} diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/ContentView.swift b/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/ContentView.swift deleted file mode 100644 index 65ed9e4..0000000 --- a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/ContentView.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// ContentView.swift -// AsyncMultiplexImage-Demo -// -// Created by Muukii on 2022/09/13. -// - -import SwiftUI - -import AsyncMultiplexImage -import AsyncMultiplexImage_Nuke -import SwiftUI -import Nuke - -struct _SlowDownloader: AsyncMultiplexImageDownloader { - - let pipeline: ImagePipeline - - init(pipeline: ImagePipeline) { - self.pipeline = pipeline - } - - func download(candidate: AsyncMultiplexImageCandidate, displaySize: CGSize) async throws -> Image { - - switch candidate.index { - case 0: - try? await Task.sleep(nanoseconds: 2_000_000_000) - case 1: - try? await Task.sleep(nanoseconds: 1_500_000_000) - case 2: - try? await Task.sleep(nanoseconds: 1_000_000_000) - case 3: - try? await Task.sleep(nanoseconds: 0_500_000_000) - default: - break - } - - let response = try await pipeline.image(for: .init(urlRequest: candidate.urlRequest)) - return .init(uiImage: response) - } - -} - -struct ContentView: View { - - @State private var basePhotoURLString: String = "https://images.unsplash.com/photo-1492446845049-9c50cc313f00" - - var body: some View { - VStack { - AsyncMultiplexImage( - multiplexImage: .init(identifier: basePhotoURLString, urls: buildURLs(basePhotoURLString)), - downloader: _SlowDownloader(pipeline: .shared) - ) { phase in - switch phase { - case .empty: - Text("Loading") - case .progress(let image): - image - .resizable() - .scaledToFill() - case .success(let image): - image - .resizable() - .scaledToFill() - case .failure(let error): - Text("Error") - } - } - - HStack { - Button("1") { - basePhotoURLString = "https://images.unsplash.com/photo-1660668377331-da480e5339a0" - } - Button("2") { - basePhotoURLString = "https://images.unsplash.com/photo-1658214764191-b002b517e9e5" - } - Button("3") { - basePhotoURLString = "https://images.unsplash.com/photo-1587126396803-be14d33e49cf" - } - } - } - .padding() - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} - -func buildURLs(_ baseURLString: String) -> [URL] { - - var components = URLComponents(string: baseURLString)! - - return [ - "", - "w=100", - "w=50", - "w=10", - ].map { - - components.query = $0 - - return components.url! - - } - -} diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.pbxproj b/Development/AsyncMultiplexImage-Demo.xcodeproj/project.pbxproj similarity index 89% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.pbxproj rename to Development/AsyncMultiplexImage-Demo.xcodeproj/project.pbxproj index e3aafcd..2761191 100644 --- a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.pbxproj +++ b/Development/AsyncMultiplexImage-Demo.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 4B57106128D0AC8000AA053C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B57106028D0AC8000AA053C /* Preview Assets.xcassets */; }; 4B57106A28D0ACA300AA053C /* AsyncMultiplexImage in Frameworks */ = {isa = PBXBuildFile; productRef = 4B57106928D0ACA300AA053C /* AsyncMultiplexImage */; }; 4B57106C28D0ACA800AA053C /* AsyncMultiplexImage-Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = 4B57106B28D0ACA800AA053C /* AsyncMultiplexImage-Nuke */; }; + 4B5AC66A2BB96A8D00641C3B /* SwiftUIHosting in Frameworks */ = {isa = PBXBuildFile; productRef = 4B5AC6692BB96A8D00641C3B /* SwiftUIHosting */; }; + 4B5AC66D2BB96AAB00641C3B /* MondrianLayout in Frameworks */ = {isa = PBXBuildFile; productRef = 4B5AC66C2BB96AAB00641C3B /* MondrianLayout */; }; 4B7E24F9296184E300E53388 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E24F8296184E300E53388 /* AppDelegate.swift */; }; /* End PBXBuildFile section */ @@ -30,6 +32,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4B5AC66D2BB96AAB00641C3B /* MondrianLayout in Frameworks */, + 4B5AC66A2BB96A8D00641C3B /* SwiftUIHosting in Frameworks */, 4B57106A28D0ACA300AA053C /* AsyncMultiplexImage in Frameworks */, 4B57106C28D0ACA800AA053C /* AsyncMultiplexImage-Nuke in Frameworks */, ); @@ -102,6 +106,8 @@ packageProductDependencies = ( 4B57106928D0ACA300AA053C /* AsyncMultiplexImage */, 4B57106B28D0ACA800AA053C /* AsyncMultiplexImage-Nuke */, + 4B5AC6692BB96A8D00641C3B /* SwiftUIHosting */, + 4B5AC66C2BB96AAB00641C3B /* MondrianLayout */, ); productName = "AsyncMultiplexImage-Demo"; productReference = 4B57105528D0AC7F00AA053C /* AsyncMultiplexImage-Demo.app */; @@ -131,6 +137,10 @@ Base, ); mainGroup = 4B57104C28D0AC7F00AA053C; + packageReferences = ( + 4B5AC6682BB96A8D00641C3B /* XCRemoteSwiftPackageReference "swiftui-hosting" */, + 4B5AC66B2BB96AAB00641C3B /* XCRemoteSwiftPackageReference "MondrianLayout" */, + ); productRefGroup = 4B57105628D0AC7F00AA053C /* Products */; projectDirPath = ""; projectRoot = ""; @@ -369,6 +379,25 @@ }; /* End XCConfigurationList section */ +/* Begin XCRemoteSwiftPackageReference section */ + 4B5AC6682BB96A8D00641C3B /* XCRemoteSwiftPackageReference "swiftui-hosting" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/FluidGroup/swiftui-hosting.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.2.0; + }; + }; + 4B5AC66B2BB96AAB00641C3B /* XCRemoteSwiftPackageReference "MondrianLayout" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/FluidGroup/MondrianLayout.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.10.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ 4B57106928D0ACA300AA053C /* AsyncMultiplexImage */ = { isa = XCSwiftPackageProductDependency; @@ -378,6 +407,16 @@ isa = XCSwiftPackageProductDependency; productName = "AsyncMultiplexImage-Nuke"; }; + 4B5AC6692BB96A8D00641C3B /* SwiftUIHosting */ = { + isa = XCSwiftPackageProductDependency; + package = 4B5AC6682BB96A8D00641C3B /* XCRemoteSwiftPackageReference "swiftui-hosting" */; + productName = SwiftUIHosting; + }; + 4B5AC66C2BB96AAB00641C3B /* MondrianLayout */ = { + isa = XCSwiftPackageProductDependency; + package = 4B5AC66B2BB96AAB00641C3B /* XCRemoteSwiftPackageReference "MondrianLayout" */; + productName = MondrianLayout; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 4B57104D28D0AC7F00AA053C /* Project object */; diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Development/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Development/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Development/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Development/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Development/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Development/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..05ce6f6 --- /dev/null +++ b/Development/AsyncMultiplexImage-Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,41 @@ +{ + "pins" : [ + { + "identity" : "mondrianlayout", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FluidGroup/MondrianLayout.git", + "state" : { + "revision" : "5f00b13984fe08316fc5b5be06e2f41c14a3befa", + "version" : "0.10.0" + } + }, + { + "identity" : "nuke", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke.git", + "state" : { + "revision" : "4625c73ea00a9fb4b4f3e28d95d0021a44af7e59", + "version" : "12.5.0" + } + }, + { + "identity" : "swiftui-hosting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FluidGroup/swiftui-hosting.git", + "state" : { + "revision" : "7e8eaca72eae910d6d3b6272c263c6c3a10b755c", + "version" : "1.2.0" + } + }, + { + "identity" : "swiftui-support", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FluidGroup/swiftui-support.git", + "state" : { + "revision" : "c610c1e46c14c4660beb4ef7fe0241941dbecdc6", + "version" : "0.5.0" + } + } + ], + "version" : 2 +} diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/xcshareddata/xcschemes/AsyncMultiplexImage-Demo.xcscheme b/Development/AsyncMultiplexImage-Demo.xcodeproj/xcshareddata/xcschemes/AsyncMultiplexImage-Demo.xcscheme similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo.xcodeproj/xcshareddata/xcschemes/AsyncMultiplexImage-Demo.xcscheme rename to Development/AsyncMultiplexImage-Demo.xcodeproj/xcshareddata/xcschemes/AsyncMultiplexImage-Demo.xcscheme diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/AppDelegate.swift b/Development/AsyncMultiplexImage-Demo/AppDelegate.swift similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/AppDelegate.swift rename to Development/AsyncMultiplexImage-Demo/AppDelegate.swift diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/Assets.xcassets/AccentColor.colorset/Contents.json b/Development/AsyncMultiplexImage-Demo/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/Assets.xcassets/AccentColor.colorset/Contents.json rename to Development/AsyncMultiplexImage-Demo/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Development/AsyncMultiplexImage-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Development/AsyncMultiplexImage-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/Assets.xcassets/Contents.json b/Development/AsyncMultiplexImage-Demo/Assets.xcassets/Contents.json similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/Assets.xcassets/Contents.json rename to Development/AsyncMultiplexImage-Demo/Assets.xcassets/Contents.json diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/AsyncMultiplexImage_Demo.entitlements b/Development/AsyncMultiplexImage-Demo/AsyncMultiplexImage_Demo.entitlements similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/AsyncMultiplexImage_Demo.entitlements rename to Development/AsyncMultiplexImage-Demo/AsyncMultiplexImage_Demo.entitlements diff --git a/Development/AsyncMultiplexImage-Demo/ContentView.swift b/Development/AsyncMultiplexImage-Demo/ContentView.swift new file mode 100644 index 0000000..fb5a0b3 --- /dev/null +++ b/Development/AsyncMultiplexImage-Demo/ContentView.swift @@ -0,0 +1,225 @@ +// +// ContentView.swift +// AsyncMultiplexImage-Demo +// +// Created by Muukii on 2022/09/13. +// + +import AsyncMultiplexImage +import AsyncMultiplexImage_Nuke +import MondrianLayout +import Nuke +import SwiftUI +import SwiftUIHosting + +struct _SlowDownloader: AsyncMultiplexImageDownloader { + + let pipeline: ImagePipeline + + init(pipeline: ImagePipeline) { + self.pipeline = pipeline + } + + func download(candidate: AsyncMultiplexImageCandidate, displaySize: CGSize) async throws + -> UIImage + { + + switch candidate.index { + case 0: + try? await Task.sleep(nanoseconds: 2_000_000_000) + case 1: + try? await Task.sleep(nanoseconds: 1_500_000_000) + case 2: + try? await Task.sleep(nanoseconds: 1_000_000_000) + case 3: + try? await Task.sleep(nanoseconds: 0_500_000_000) + default: + break + } + + let response = try await pipeline.image(for: .init(urlRequest: candidate.urlRequest)) + return response + } + +} + +struct ContentView: View { + + @State private var basePhotoURLString: String = + "https://images.unsplash.com/photo-1492446845049-9c50cc313f00" + + var body: some View { + NavigationView { + Form { + Section { + NavigationLink("SwiftUI") { + VStack { + AsyncMultiplexImage( + multiplexImage: .init( + identifier: basePhotoURLString, + urls: buildURLs(basePhotoURLString) + ), + downloader: _SlowDownloader(pipeline: .shared) + ) { phase in + switch phase { + case .empty: + Text("Loading") + case .progress(let image): + image + .resizable() + .scaledToFill() + case .success(let image): + image + .resizable() + .scaledToFill() + case .failure(let error): + Text("Error") + } + } + + HStack { + Button("1") { + basePhotoURLString = + "https://images.unsplash.com/photo-1660668377331-da480e5339a0" + } + Button("2") { + basePhotoURLString = + "https://images.unsplash.com/photo-1658214764191-b002b517e9e5" + } + Button("3") { + basePhotoURLString = + "https://images.unsplash.com/photo-1587126396803-be14d33e49cf" + } + } + } + .padding() + .navigationTitle("SwiftUI") + } + NavigationLink("UIKit") { + UIKitContentViewRepresentable() + } + } + .navigationTitle("Multiplex Image") + } + } + } +} + +struct UIKitContentViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> UIKitContentView { + .init() + } + + func updateUIView(_ uiView: UIKitContentView, context: Context) { + + } + +} + +final class UIKitContentView: UIView { + + private let imageView: AsyncMultiplexImageView = .init( + downloader: _SlowDownloader(pipeline: .shared), + clearsContentBeforeDownload: true + ) + + init() { + + super.init(frame: .null) + + imageView.backgroundColor = .init(white: 0.5, alpha: 0.2) + + let buttonsView = SwiftUIHostingView { [imageView] in + HStack { + Button("1") { + + let basePhotoURLString = "https://images.unsplash.com/photo-1660668377331-da480e5339a0" + + imageView.setMultiplexImage( + .init( + identifier: basePhotoURLString, + urls: buildURLs(basePhotoURLString) + ) + ) + + } + Button("2") { + let basePhotoURLString = "https://images.unsplash.com/photo-1658214764191-b002b517e9e5" + + imageView.setMultiplexImage( + .init( + identifier: basePhotoURLString, + urls: buildURLs(basePhotoURLString) + ) + ) + + } + Button("3") { + let basePhotoURLString = "https://images.unsplash.com/photo-1587126396803-be14d33e49cf" + + imageView.setMultiplexImage( + .init( + identifier: basePhotoURLString, + urls: buildURLs(basePhotoURLString) + ) + ) + } + } + } + + Mondrian.buildSubviews(on: self) { + VStackBlock { + imageView + .viewBlock + .size(.init(width: 300, height: 300)) + buttonsView + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +@available(iOS 17, *)#Preview("UIKit"){ + let view = AsyncMultiplexImageView( + downloader: _SlowDownloader(pipeline: .shared), + clearsContentBeforeDownload: true + ) + view.setMultiplexImage( + .init( + identifier: "https://images.unsplash.com/photo-1660668377331-da480e5339a0", + urls: buildURLs("https://images.unsplash.com/photo-1660668377331-da480e5339a0") + ) + ) + view.frame = .init(origin: .zero, size: .init(width: 300, height: 300)) + return view +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} + +func buildURLs(_ baseURLString: String) -> [URL] { + + var components = URLComponents(string: baseURLString)! + + return [ + "", + "w=100", + "w=50", + "w=10", + ].map { + + components.query = $0 + + return components.url! + + } + +} diff --git a/AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/Preview Content/Preview Assets.xcassets/Contents.json b/Development/AsyncMultiplexImage-Demo/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from AsyncMultiplexImage-Demo/AsyncMultiplexImage-Demo/Preview Content/Preview Assets.xcassets/Contents.json rename to Development/AsyncMultiplexImage-Demo/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/Sources/AsyncMultiplexImage-Nuke/AsyncMultiplexImageNukeDownloader.swift b/Sources/AsyncMultiplexImage-Nuke/AsyncMultiplexImageNukeDownloader.swift index defef7a..cd66e51 100644 --- a/Sources/AsyncMultiplexImage-Nuke/AsyncMultiplexImageNukeDownloader.swift +++ b/Sources/AsyncMultiplexImage-Nuke/AsyncMultiplexImageNukeDownloader.swift @@ -17,7 +17,7 @@ public struct AsyncMultiplexImageNukeDownloader: AsyncMultiplexImageDownloader { } public func download(candidate: AsyncMultiplexImageCandidate, displaySize: CGSize) async throws - -> Image + -> UIImage { #if DEBUG @@ -41,7 +41,7 @@ public struct AsyncMultiplexImageNukeDownloader: AsyncMultiplexImageDownloader { ) ) - return .init(uiImage: response) + return response } } diff --git a/Sources/AsyncMultiplexImage/AsyncMultiplexImage.swift b/Sources/AsyncMultiplexImage/AsyncMultiplexImage.swift index 9329425..93175e8 100644 --- a/Sources/AsyncMultiplexImage/AsyncMultiplexImage.swift +++ b/Sources/AsyncMultiplexImage/AsyncMultiplexImage.swift @@ -58,8 +58,13 @@ extension OSLog { OSLog.init(subsystem: "app.muukii", category: "default") } static let view: OSLog = makeOSLogInDebug { - OSLog.init(subsystem: "app.muukii", category: "View") + OSLog.init(subsystem: "app.muukii", category: "SwiftUIVersion") } + + static let uiKit: OSLog = makeOSLogInDebug { + OSLog.init(subsystem: "app.muukii", category: "UIKitVersion") + } + } @MainActor @@ -71,7 +76,7 @@ public final class DownloadManager { public protocol AsyncMultiplexImageDownloader { - func download(candidate: AsyncMultiplexImageCandidate, displaySize: CGSize) async throws -> Image + func download(candidate: AsyncMultiplexImageCandidate, displaySize: CGSize) async throws -> UIImage } public enum AsyncMultiplexImagePhase { @@ -100,7 +105,7 @@ public struct MultiplexImage: Hashable { public let identifier: String - fileprivate private(set) var _urlsProvider: @MainActor (CGSize) -> [URL] + private(set) var _urlsProvider: @MainActor (CGSize) -> [URL] public init( identifier: String, @@ -152,7 +157,7 @@ public struct AsyncMultiplexImage? @@ -172,6 +177,7 @@ private final class _AsyncMultiplexImageViewModel: ObservableObject { guard task.isCancelled == false else { return } task.cancel() } + } private struct _AsyncMultiplexImage: View @@ -216,9 +222,9 @@ private struct _AsyncMultiplexImage 0 && newSize.width > 0 else { + return + } + + if clearsContentBeforeDownload { + imageView.image = nil + } + + // making new candidates + let urls = image._urlsProvider(newSize) + + let candidates = urls.enumerated().map { i, e in + AsyncMultiplexImageCandidate(index: i, urlRequest: .init(url: e)) + } + + // start download + + let currentTask = Task { [downloader] in + // this instance will be alive until finish + let container = ResultContainer() + let stream = await container.make( + candidates: candidates, + downloader: downloader, + displaySize: newSize + ) + + do { + for try await item in stream { + + // TODO: support custom animation + + await MainActor.run { + CATransaction.begin() + let transition = CATransition() + transition.duration = 0.13 + switch item { + case .progress(let image): + imageView.image = image + case .final(let image): + imageView.image = image + } + self.layer.add(transition, forKey: "transition") + CATransaction.commit() + } + + } + + Log.debug(.uiKit, "download finished") + } catch { + // FIXME: Error handling + } + } + + viewModel.registerCurrentTask(currentTask) + } + + private func unloadImage() { + + weak var _image = imageView.image + imageView.image = nil + + #if DEBUG + if _image != nil { + Log.debug(.uiKit, "\(String(describing: _image)) was not deallocated afeter unload") + } + #endif + + } +} +#endif