diff --git a/AsyncImageView/AsyncSwiftUIImageView.swift b/AsyncImageView/AsyncSwiftUIImageView.swift index 0261dab0..4f17e2b7 100644 --- a/AsyncImageView/AsyncSwiftUIImageView.swift +++ b/AsyncImageView/AsyncSwiftUIImageView.swift @@ -7,7 +7,6 @@ // import SwiftUI -import Combine import ReactiveSwift public struct AsyncSwiftUIImageView< @@ -22,32 +21,25 @@ public struct AsyncSwiftUIImageView< PlaceholderRenderer.Data == Data, PlaceholderRenderer.Error == Never, Renderer.RenderResult == PlaceholderRenderer.RenderResult { - private typealias ImageLoader = AsyncImageLoader - - private let renderer: Renderer - private let placeholderRenderer: PlaceholderRenderer? - private let uiScheduler: ReactiveSwift.Scheduler - private let requestsSignal: Signal - private let requestsObserver: Signal.Observer + private typealias ViewModel = AsyncSwiftUIImageViewModel - private let imageCreationScheduler: ReactiveSwift.Scheduler + @State private var viewModel: ViewModel public init( renderer: Renderer, placeholderRenderer: PlaceholderRenderer? = nil, uiScheduler: ReactiveSwift.Scheduler = UIScheduler(), imageCreationScheduler: ReactiveSwift.Scheduler = QueueScheduler()) { - self.renderer = renderer - self.placeholderRenderer = placeholderRenderer - self.uiScheduler = uiScheduler - self.imageCreationScheduler = imageCreationScheduler - - (self.requestsSignal, self.requestsObserver) = Signal.pipe() + _viewModel = State( + initialValue: ViewModel( + renderer: renderer, + placeholderRenderer: placeholderRenderer, + uiScheduler: uiScheduler, + imageCreationScheduler: imageCreationScheduler + ) + ) } - @State private var renderResult: Renderer.RenderResult? - @State private var disposable: Disposable? - public var data: ImageViewData? { didSet { self.requestImage() @@ -74,28 +66,17 @@ Renderer.RenderResult == PlaceholderRenderer.RenderResult { } } .onAppear { - self.disposable?.dispose() - self.disposable = ImageLoader.createSignal( - requestsSignal: self.requestsSignal, - renderer: self.renderer, - placeholderRenderer: self.placeholderRenderer, - uiScheduler: self.uiScheduler, - imageCreationScheduler: self.imageCreationScheduler - ) - .observeValues { - self.renderResult = $0 - } - + self.viewModel.start() self.requestImage() } .onDisappear { - self.disposable?.dispose() + self.viewModel.stop() } } @ViewBuilder private var imageView: some View { - if let result = self.renderResult { + if let result = self.viewModel.renderResult { Image(uiImage: result.image) .resizable() .scaledToFit() @@ -116,9 +97,7 @@ Renderer.RenderResult == PlaceholderRenderer.RenderResult { return } - self.imageCreationScheduler.schedule { [data, size, weak observer = self.requestsObserver] in - observer?.send(value: data?.renderDataWithSize(size)) - } + self.viewModel.requestImage(self.data, size: self.size) } } @@ -151,3 +130,70 @@ private struct SizeModifier: ViewModifier { ) } } + +@Observable +private final class AsyncSwiftUIImageViewModel< + Data: RenderDataType, + ImageViewData: ImageViewDataType, + Renderer: RendererType, + PlaceholderRenderer: RendererType +> + where + ImageViewData.RenderData == Data, + Renderer.Data == Data, + Renderer.Error == Never, + PlaceholderRenderer.Data == Data, + PlaceholderRenderer.Error == Never, + Renderer.RenderResult == PlaceholderRenderer.RenderResult { + private typealias ImageLoader = AsyncImageLoader + + private(set) var renderResult: Renderer.RenderResult? + + private let renderer: Renderer + private let placeholderRenderer: PlaceholderRenderer? + private let uiScheduler: ReactiveSwift.Scheduler + private let imageCreationScheduler: ReactiveSwift.Scheduler + + private let requestsSignal: Signal + private let requestsObserver: Signal.Observer + @ObservationIgnored + private var disposable: Disposable? + + init( + renderer: Renderer, + placeholderRenderer: PlaceholderRenderer?, + uiScheduler: ReactiveSwift.Scheduler, + imageCreationScheduler: ReactiveSwift.Scheduler) { + self.renderer = renderer + self.placeholderRenderer = placeholderRenderer + self.uiScheduler = uiScheduler + self.imageCreationScheduler = imageCreationScheduler + + (self.requestsSignal, self.requestsObserver) = Signal.pipe() + } + + func start() { + self.disposable?.dispose() + self.disposable = ImageLoader.createSignal( + requestsSignal: self.requestsSignal, + renderer: self.renderer, + placeholderRenderer: self.placeholderRenderer, + uiScheduler: self.uiScheduler, + imageCreationScheduler: self.imageCreationScheduler + ) + .observeValues { [weak self] result in + self?.renderResult = result + } + } + + func stop() { + self.disposable?.dispose() + self.disposable = nil + } + + func requestImage(_ data: ImageViewData?, size: CGSize) { + self.imageCreationScheduler.schedule { [data, size, observer = self.requestsObserver] in + observer.send(value: data?.renderDataWithSize(size)) + } + } +} diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 15f4356e..e1a057b0 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -258,7 +258,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -313,7 +313,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; diff --git a/Package.swift b/Package.swift index f8de81be..5d623855 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,14 @@ -// swift-tools-version:5.7 +// swift-tools-version:6.0 import PackageDescription let package = Package( name: "AsyncImageView", - platforms: [.iOS(.v13), .tvOS(.v13), .watchOS(.v9)], + platforms: [ + .iOS(.v18), + .macOS(.v15), + .tvOS(.v18), + .watchOS(.v11) + ], products: [ .library(name: "AsyncImageView", targets: ["AsyncImageView"]) ],