Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 81 additions & 35 deletions AsyncImageView/AsyncSwiftUIImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

import SwiftUI
import Combine
import ReactiveSwift

public struct AsyncSwiftUIImageView<
Expand All @@ -22,32 +21,25 @@ public struct AsyncSwiftUIImageView<
PlaceholderRenderer.Data == Data,
PlaceholderRenderer.Error == Never,
Renderer.RenderResult == PlaceholderRenderer.RenderResult {
private typealias ImageLoader = AsyncImageLoader<Data, ImageViewData, Renderer, PlaceholderRenderer>

private let renderer: Renderer
private let placeholderRenderer: PlaceholderRenderer?
private let uiScheduler: ReactiveSwift.Scheduler
private let requestsSignal: Signal<Data?, Never>
private let requestsObserver: Signal<Data?, Never>.Observer
private typealias ViewModel = AsyncSwiftUIImageViewModel<Data, ImageViewData, Renderer, PlaceholderRenderer>

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()
Expand All @@ -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()
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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<Data, ImageViewData, Renderer, PlaceholderRenderer>

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<Data?, Never>
private let requestsObserver: Signal<Data?, Never>.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))
}
}
}
4 changes: 2 additions & 2 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -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"])
],
Expand Down
Loading