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
117 changes: 58 additions & 59 deletions AsyncImageView/AsyncImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,60 +21,59 @@ public protocol ImageViewDataType {

/// A `UIImageView` that can render asynchronously.
open class AsyncImageView<
Data: RenderDataType,
Data: RenderDataType,
ImageViewData: ImageViewDataType,
Renderer: RendererType,
PlaceholderRenderer: RendererType>: UIImageView
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>
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 let requestsSignal: Signal<Data?, Never>
private let requestsObserver: Signal<Data?, Never>.Observer

private let imageCreationScheduler: ReactiveSwift.Scheduler
private let imageCreationScheduler: ReactiveSwift.Scheduler

private var disposable: Disposable?

private var disposable: Disposable?

public init(
initialFrame: CGRect,
renderer: Renderer,
placeholderRenderer: PlaceholderRenderer? = nil,
uiScheduler: ReactiveSwift.Scheduler = UIScheduler(),
imageCreationScheduler: ReactiveSwift.Scheduler = QueueScheduler())
{
(self.requestsSignal, self.requestsObserver) = Signal.pipe()
self.imageCreationScheduler = imageCreationScheduler

super.init(frame: initialFrame)

self.backgroundColor = nil
self.disposable = ImageLoader.createSignal(
requestsSignal: self.requestsSignal,
renderer: renderer,
placeholderRenderer: placeholderRenderer,
uiScheduler: uiScheduler,
imageCreationScheduler: imageCreationScheduler
)
.observeValues { [weak self] result in
self?.updateImage(result)
}
uiScheduler: ReactiveSwift.Scheduler = UIScheduler(),
imageCreationScheduler: ReactiveSwift.Scheduler = QueueScheduler()
) {
(self.requestsSignal, self.requestsObserver) = Signal.pipe()
self.imageCreationScheduler = imageCreationScheduler

super.init(frame: initialFrame)

self.backgroundColor = nil

self.disposable = ImageLoader.createSignal(
requestsSignal: self.requestsSignal,
renderer: renderer,
placeholderRenderer: placeholderRenderer,
uiScheduler: uiScheduler,
imageCreationScheduler: imageCreationScheduler
)
.observeValues { [weak self] result in
self?.updateImage(result)
}
}

public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

deinit {
self.disposable?.dispose()
}
deinit {
self.disposable?.dispose()
}

open override var frame: CGRect {
didSet {
Expand All @@ -93,19 +92,19 @@ open class AsyncImageView<
self.requestNewImageIfReady()
}
}
open override func didMoveToWindow() {
super.didMoveToWindow()
if self.window != nil {
self.requestNewImageIfReady()
}
}

open override func didMoveToWindow() {
super.didMoveToWindow()

if self.window != nil {
self.requestNewImageIfReady()
}
}

// MARK: -

private func requestNewImageIfReady() {
if self.window != nil && self.bounds.size.width > 0 && self.bounds.size.height > 0 {
if self.window != nil && self.bounds.size.width > 0 && self.bounds.size.height > 0 {
self.requestNewImage(self.bounds.size, data: self.data)
}
}
Expand All @@ -121,21 +120,21 @@ open class AsyncImageView<
// MARK: -

private func updateImage(_ result: Renderer.RenderResult?) {
if let result = result {
if result.cacheHit {
self.image = result.image
} else {
UIView.transition(
with: self,
duration: fadeAnimationDuration,
options: [.curveEaseOut, .transitionCrossDissolve],
animations: { self.image = result.image },
completion: nil
)
}
} else {
self.image = nil
}
if let result = result {
if result.cacheHit {
self.image = result.image
} else {
UIView.transition(
with: self,
duration: fadeAnimationDuration,
options: [.curveEaseOut, .transitionCrossDissolve],
animations: { self.image = result.image },
completion: nil
)
}
} else {
self.image = nil
}
}
}

Expand Down
21 changes: 10 additions & 11 deletions AsyncImageView/AsyncSwiftUIImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@ Renderer.RenderResult == PlaceholderRenderer.RenderResult {
private let uiScheduler: ReactiveSwift.Scheduler
private let requestsSignal: Signal<Data?, Never>
private let requestsObserver: Signal<Data?, Never>.Observer

private let imageCreationScheduler: ReactiveSwift.Scheduler

public init(
renderer: Renderer,
placeholderRenderer: PlaceholderRenderer? = nil,
uiScheduler: ReactiveSwift.Scheduler = UIScheduler(),
imageCreationScheduler: ReactiveSwift.Scheduler = QueueScheduler())
{
imageCreationScheduler: ReactiveSwift.Scheduler = QueueScheduler()) {
self.renderer = renderer
self.placeholderRenderer = placeholderRenderer
self.uiScheduler = uiScheduler
Expand All @@ -48,13 +47,13 @@ Renderer.RenderResult == PlaceholderRenderer.RenderResult {

@State private var renderResult: Renderer.RenderResult?
@State private var disposable: Disposable?

public var data: ImageViewData? {
didSet {
self.requestImage()
}
}

@State
private var size: CGSize = .zero {
didSet {
Expand All @@ -63,11 +62,11 @@ Renderer.RenderResult == PlaceholderRenderer.RenderResult {
}
}
}

public var body: some View {
ZStack {
self.imageView

Color.clear
.modifier(SizeModifier())
.onPreferenceChange(ImageSizePreferenceKey.self) { imageSize in
Expand Down Expand Up @@ -111,7 +110,7 @@ Renderer.RenderResult == PlaceholderRenderer.RenderResult {
Color.clear
}
}

private func requestImage() {
guard self.size.width > 0 && self.size.height > 0 else {
return
Expand All @@ -134,9 +133,9 @@ public extension AsyncSwiftUIImageView {

private struct ImageSizePreferenceKey: PreferenceKey {
typealias Value = CGSize

static var defaultValue: CGSize = .zero

static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
Expand Down
3 changes: 1 addition & 2 deletions AsyncImageView/Renderers/AnyRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public final class AnyRenderer<

/// Creates an `AnyRenderer` based on another `RendererType`.
public convenience init<R: RendererType>(_ renderer: R)
where R.Data == Data, R.RenderResult == RenderResult, R.Error == Error
{
where R.Data == Data, R.RenderResult == RenderResult, R.Error == Error {
self.init(renderBlock: renderer.renderImageWithData)
}

Expand Down
7 changes: 2 additions & 5 deletions AsyncImageView/Renderers/CacheRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ public final class CacheRenderer<
>: RendererType
where
Cache.Key == Renderer.Data,
Cache.Value == Renderer.RenderResult
{
Cache.Value == Renderer.RenderResult {
private let renderer: Renderer
private let cache: Cache

Expand Down Expand Up @@ -51,8 +50,7 @@ public final class CacheRenderer<
extension RendererType {
/// Surrounds this renderer with a layer of caching.
public func withCache<Cache: CacheType>(_ cache: Cache) -> CacheRenderer<Self, Cache>
where Cache.Key == Self.Data, Cache.Value == Self.RenderResult
{
where Cache.Key == Self.Data, Cache.Value == Self.RenderResult {
return CacheRenderer(renderer: self, cache: cache)
}
}
Expand Down Expand Up @@ -90,4 +88,3 @@ private extension UIImage {
)
}
}

12 changes: 6 additions & 6 deletions AsyncImageView/Renderers/ContextRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import CoreGraphics
#if !os(watchOS)

/// `SynchronousRendererType` which generates a `UIImage` by rendering into a context.
@available(iOS 10.0, tvOSApplicationExtension 10.0, *)
@available(iOS 10.0, tvOSApplicationExtension 10.0, *)
public final class ContextRenderer<Data: RenderDataType>: SynchronousRendererType {
public typealias Block = (_ context: CGContext, _ data: Data) -> ()
public typealias Block = (_ context: CGContext, _ data: Data) -> Void

private let format: UIGraphicsImageRendererFormat
private let imageSize: CGSize?
private let renderingBlock: Block

/// - opaque: A Boolean flag indicating whether the bitmap is opaque.
/// If you know the bitmap is fully opaque, specify YES to ignore the
/// alpha channel and optimize the bitmap’s storage.
Expand All @@ -33,13 +33,13 @@ public final class ContextRenderer<Data: RenderDataType>: SynchronousRendererTyp
self.imageSize = imageSize
self.renderingBlock = renderingBlock
}

public func renderImageWithData(_ data: Data) -> UIImage {
let renderer = UIGraphicsImageRenderer(
size: self.imageSize ?? data.size,
format: self.format
)

return renderer.image { context in
self.renderingBlock(context.cgContext, data)
}
Expand Down
10 changes: 5 additions & 5 deletions AsyncImageView/Renderers/ErrorIgnoringRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import ReactiveSwift
/// if you're already providing a placeholder renderer.
public final class ErrorIgnoringRenderer<Renderer: RendererType>: RendererType {
private let renderer: Renderer
private let handler: ((Renderer.Error) -> ())?
private let handler: ((Renderer.Error) -> Void)?

public init(renderer: Renderer, handler: ((Renderer.Error) -> ())?) {
public init(renderer: Renderer, handler: ((Renderer.Error) -> Void)?) {
self.renderer = renderer
self.handler = handler
}
Expand All @@ -27,7 +27,7 @@ public final class ErrorIgnoringRenderer<Renderer: RendererType>: RendererType {
.renderImageWithData(data)
.flatMapError { [handler = self.handler] error in
handler?(error)

return .empty
}
}
Expand All @@ -38,9 +38,9 @@ extension RendererType {
public func ignoreErrors() -> ErrorIgnoringRenderer<Self> {
return ErrorIgnoringRenderer(renderer: self, handler: nil)
}

/// Returns a new `RendererType` that will ignore any errors emitted by the receiver.
public func logAndIgnoreErrors(handler: @escaping (Self.Error) -> ()) -> ErrorIgnoringRenderer<Self> {
public func logAndIgnoreErrors(handler: @escaping (Self.Error) -> Void) -> ErrorIgnoringRenderer<Self> {
return ErrorIgnoringRenderer(renderer: self, handler: handler)
}
}
6 changes: 2 additions & 4 deletions AsyncImageView/Renderers/FallbackRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ public final class FallbackRenderer<
R1.RenderResult == RR1,
R2.RenderResult == RR2,
R1.Error == E1,
R2.Error == E2
{
R2.Error == E2 {
self.primaryRenderer = AnyRenderer(primaryRenderer)
self.fallbackRenderer = AnyRenderer(fallbackRenderer)
}
Expand All @@ -52,8 +51,7 @@ extension RendererType {
/// Uses the given `RendererType` whenever `self` produces an error.
public func fallback<Other: RendererType>
(_ fallbackRenderer: Other) -> FallbackRenderer<Self.Data, Self.RenderResult, Other.RenderResult, Self.Error, Other.Error>
where Self.Data == Other.Data
{
where Self.Data == Other.Data {
return FallbackRenderer(primaryRenderer: self, fallbackRenderer: fallbackRenderer)
}
}
Expand Down
Loading