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
35 changes: 23 additions & 12 deletions Sources/AsyncMultiplexImage/AsyncMultiplexImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public struct AsyncMultiplexImageCandidate: Hashable {

public struct AsyncMultiplexImage<Content: View, Downloader: AsyncMultiplexImageDownloader>: View {

private var candidates: [AsyncMultiplexImageCandidate] = []
@State private var candidates: [AsyncMultiplexImageCandidate] = []

@State private var internalView: _AsyncMultiplexImage<Content, Downloader>?

Expand Down Expand Up @@ -129,8 +129,8 @@ public struct AsyncMultiplexImage<Content: View, Downloader: AsyncMultiplexImage

let candidates = urls.enumerated().map { i, e in AsyncMultiplexImageCandidate(index: i, urlRequest: .init(url: e)) }

self.candidates = candidates
self.internalView = .init(candidates: candidates, downloader: downloader, content: content)
Log.debug(.view, newValue)
})
.id(candidates) // to make distinct views for each image-set.
}
Expand All @@ -140,7 +140,7 @@ public struct AsyncMultiplexImage<Content: View, Downloader: AsyncMultiplexImage

struct _AsyncMultiplexImage<Content: View, Downloader: AsyncMultiplexImageDownloader>: View {

@State private var currentImage: Image?
@State private var item: ResultContainer.Item?
@State private var task: Task<Void, Never>?

private let downloader: Downloader
Expand All @@ -160,14 +160,20 @@ struct _AsyncMultiplexImage<Content: View, Downloader: AsyncMultiplexImageDownlo

var body: some View {

Group {
GeometryReader { proxy in
content({
if let currentImage {
return .success(currentImage)
switch item {
case .none:
return .empty
case .some(.progress(let image)):
return .progress(image)
case .some(.final(let image)):
return .success(image)
}
return .empty
}())
.frame(width: proxy.size.width, height: proxy.size.height)
}
.clipped()
.onAppear {

let currentTask = Task {
Expand All @@ -176,8 +182,8 @@ struct _AsyncMultiplexImage<Content: View, Downloader: AsyncMultiplexImageDownlo
let stream = await container.make(candidates: candidates, on: downloader)

do {
for try await image in stream {
currentImage = image
for try await item in stream {
self.item = item
}
} catch {
// FIXME: Error handling
Expand All @@ -196,6 +202,11 @@ struct _AsyncMultiplexImage<Content: View, Downloader: AsyncMultiplexImageDownlo

actor ResultContainer {

enum Item {
case progress(Image)
case final(Image)
}

var lastCandidate: AsyncMultiplexImageCandidate? = nil

var idealImageTask: Task<Void, Never>?
Expand All @@ -209,7 +220,7 @@ actor ResultContainer {
func make<Downloader: AsyncMultiplexImageDownloader>(
candidates: [AsyncMultiplexImageCandidate],
on downloader: Downloader
) -> AsyncThrowingStream<Image, Error> {
) -> AsyncThrowingStream<Item, Error> {

Log.debug(.`generic`, "Load: \(candidates.map { $0.urlRequest })")

Expand Down Expand Up @@ -246,7 +257,7 @@ actor ResultContainer {
Log.debug(.`generic`, "Loaded ideal")

lastCandidate = idealCandidate
continuation.yield(result)
continuation.yield(.final(result))
} catch {
continuation.yield(with: .failure(error))
}
Expand Down Expand Up @@ -290,7 +301,7 @@ actor ResultContainer {

lastCandidate = idealCandidate

let yieldResult = continuation.yield(result)
let yieldResult = continuation.yield(.progress(result))

Log.debug(.`generic`, "Loaded progress image => \(candidate.index), \(yieldResult)")
} catch {
Expand Down
106 changes: 82 additions & 24 deletions Sources/AsyncMultiplexImageDemo/Demo.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import AsyncMultiplexImage
import AsyncMultiplexImage_Nuke
import SwiftUI
import Nuke
import SwiftUI

func buildURLs(baseURLString: String, size: CGSize) -> [URL] {

func buildURLs() -> [URL] {

let baseURLString = "https://images.unsplash.com/photo-1492446845049-9c50cc313f00?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8"

var components = URLComponents(string: baseURLString)!

return [
Expand All @@ -16,33 +13,94 @@ let baseURLString = "https://images.unsplash.com/photo-1492446845049-9c50cc313f0
"w=50",
"w=10",
].map {

components.query = $0

return components.url!

}

}

struct AsyncMultiplexImage_Previews: PreviewProvider {
static var previews: some View {
AsyncMultiplexImage(
urls: buildURLs(),
downloader: SlowDownloader(pipeline: .shared)
) { phase in
switch phase {
case .empty:
Text("Loading")
case .progress(let image):
image
case .success(let image):
image
.resizable()
.scaledToFit()
case .failure(let error):
Text("Error")

Group {
AsyncMultiplexImage(
urlsProvider: { size in
buildURLs(
baseURLString:
"https://images.unsplash.com/photo-1492446845049-9c50cc313f00?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8",
size: size
)
},
downloader: SlowDownloader(pipeline: .shared)
) { phase in
switch phase {
case .empty:
Rectangle()
.foregroundColor(.yellow)
.overlay(Text("Loading"))
case .progress(let image):
image
.resizable()
.scaledToFill()
.overlay(Text("Progress"))

case .success(let image):
image
.resizable()
.scaledToFill()
.overlay(Text("Done"))
case .failure(let error):
Text("Error")
}
}
.frame(width: 300, height: 300)
.overlay(Color.red.opacity(0.3))
}
}
}

struct BookAlign: View, PreviewProvider {
var body: some View {
if #available(iOS 15, *) {
Content()
}
}

static var previews: some View {
Self()
}

@available(iOS 15, *)
private struct Content: View {

var body: some View {
ZStack {
AsyncImage(
url: .init(
string:
"https://images.unsplash.com/photo-1492446845049-9c50cc313f00?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8"
)!
) { phase in
switch phase {
case .empty:
Rectangle()
.foregroundColor(.yellow)
.overlay(Text("Loading"))
case .success(let image):
image
.resizable()
.scaledToFill()
.overlay(Text("Done"))
case .failure(let error):
Text("Error")
}
}
}
.frame(width: 200, height: 200)
.overlay(Color.red.opacity(0.3))
}
}
}