Skip to content

Commit 1d43d72

Browse files
committed
Auto-cancel requests if needed
1 parent 875cf2b commit 1d43d72

File tree

2 files changed

+64
-13
lines changed

2 files changed

+64
-13
lines changed

README.md

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,64 @@
99
> **Note**. This is an API preview. It is not battle-tested yet, and might change in the future.
1010
1111
## Usage
12-
1312
Here is an example of using `FetchImage` in a custom SwiftUI view.
1413

1514
```swift
1615
public struct ImageView: View {
17-
@ObservedObject var image: FetchImage
16+
let url: URL
17+
18+
@StateObject private var image = FetchImage()
1819

1920
public var body: some View {
2021
ZStack {
2122
Rectangle().fill(Color.gray)
2223
image.view?
2324
.resizable()
2425
.aspectRatio(contentMode: .fill)
26+
.clipped()
2527
}
2628

2729
// Cancel and restart the request during scrolling
2830
// If the view is still on screen, use `cancel()` instead of `reset()`.
29-
.onAppear(perform: image.fetch)
31+
.onAppear {
32+
// Ensure that synchronous cache lookup doesn't trigger animations
33+
withoutAnimation {
34+
image.load(url)
35+
}
36+
}
3037
.onDisappear(perform: image.reset)
3138

3239
// (Optional) Animate image appearance
3340
.animation(.default)
3441
}
3542
}
3643

37-
struct ImageView_Previews: PreviewProvider {
38-
static var previews: some View {
39-
let url = URL(string: "https://cloud.githubusercontent.com/assets/1567433/9781817/ecb16e82-57a0-11e5-9b43-6b4f52659997.jpg")!
40-
return ImageView(image: FetchImage(url: url))
41-
.frame(width: 80, height: 80)
42-
.clipped()
44+
private func withoutAnimation(_ closure: () -> Void) {
45+
var transaction = Transaction(animation: nil)
46+
transaction.disablesAnimations = true
47+
withTransaction(transaction, closure)
48+
}
49+
```
50+
> For iOS 13, use `@ObservedObject`.
51+
> WARNING: `@ObservedObject` does own the instance, you need to maintain the strong references to the `FetchImage` instances somewhere else.
52+
53+
### Lists
54+
`FetchImage` may also be used in a `List`:
55+
56+
```swift
57+
struct DetailsView: View {
58+
@State var refresh: Bool = false
59+
60+
var body: some View {
61+
List(imageUrls, id: \.self) {
62+
ImageView(url: $0)
63+
.frame(height: 200)
64+
}
4365
}
4466
}
4567
```
4668

69+
### Priority
4770
`FetchImage` gives you full control over how to manage the download and how to display the image. For example, one thing that you could do is to replace `onAppear` and `onDisappear` hooks to lower the priority of the requests instead of cancelling them. This might be useful if you want to continue loading and caching the images even if the user leaves the screen, but you still want the images the are currently on screen to be downloaded first.
4871

4972
```swift
@@ -56,6 +79,7 @@ struct ImageView_Previews: PreviewProvider {
5679
}
5780
```
5881

82+
### Firebase
5983
You may also initialize a `FetchImage` using a Firestore `StorageReference`. These references can be easily created synchronously, but require an asynchronous call in order generate URLs for fetching the requested content. Unfortunately, this makes image loading in SwiftUI rather difficult. Using `Nuke` and `Firebase` together simplifies the whole process quite a bit:
6084

6185
```swift
@@ -69,7 +93,7 @@ public var body: some View {
6993

7094
## Overview
7195

72-
`FetchImage` is an observable object (`ObservableObject`) that allows you to manage the download of a single image and observe the results of the download. All of the changes to the download state are published using properties marked with `@Published` property wrapper.
96+
`FetchImage` is a state object, `StateObject` (use `ObservableObject` on iOS 13) that allows you to manage the download of a single image and observe the results of the download. All of the changes to the download state are published using properties marked with `@Published` property wrapper.
7397

7498
```swift
7599
public final class FetchImage: ObservableObject, Identifiable {
@@ -144,7 +168,7 @@ FetchImage(regularUrl: highQualityUrl, lowDataUrl: lowQualityUrl)
144168

145169
| Nuke | Swift | Xcode | Platforms |
146170
|---------------|-----------------|-----------------|---------------------------------------------------|
147-
| FetchImage | Swift 5.1 | Xcode 11.3 | iOS 13.0 / watchOS 6.0 / macOS 10.15 / tvOS 13.0 |
171+
| FetchImage | Swift 5.3 | Xcode 12 | iOS 13.0 / watchOS 6.0 / macOS 10.15 / tvOS 13.0 |
148172

149173
# License
150174

Source/FetchImage.swift

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,35 @@ public final class FetchImage: ObservableObject, Identifiable {
1717
// MARK: - Paramaters
1818

1919
/// The original request.
20-
public private(set) var request: ImageRequest?
20+
public private(set) var request: ImageRequest? {
21+
didSet {
22+
assert(Thread.isMainThread, "Only modify the request from the main thread.")
23+
if currentlyLoadingImageQuality == .regular {
24+
cancel()
25+
}
26+
guard let newRequest = request else {
27+
if loadedImageQuality == .regular {
28+
image = nil
29+
}
30+
return
31+
}
32+
priority = newRequest.priority
33+
}
34+
}
2135

2236
/// The request to be performed if the original request fails with
2337
/// `networkUnavailableReason` `.constrained` (low data mode).
24-
public private(set) var lowDataRequest: ImageRequest?
38+
public private(set) var lowDataRequest: ImageRequest? {
39+
didSet {
40+
assert(Thread.isMainThread, "Only modify the request from the main thread.")
41+
if currentlyLoadingImageQuality == .low {
42+
cancel()
43+
}
44+
if lowDataRequest == nil && loadedImageQuality == .low {
45+
image = nil
46+
}
47+
}
48+
}
2549

2650
/// Returns the fetched image.
2751
///
@@ -56,6 +80,7 @@ public final class FetchImage: ObservableObject, Identifiable {
5680
public var pipeline: ImagePipeline = .shared
5781
private var task: ImageTask?
5882
private var loadedImageQuality: ImageQuality?
83+
private var currentlyLoadingImageQuality: ImageQuality? = nil
5984

6085
private enum ImageQuality {
6186
case regular
@@ -200,6 +225,7 @@ public final class FetchImage: ObservableObject, Identifiable {
200225

201226
private func load(request: ImageRequest, quality: ImageQuality) {
202227
progress = Progress(completed: 0, total: 0)
228+
currentlyLoadingImageQuality = quality
203229

204230
task = pipeline.loadImage(
205231
with: request,
@@ -224,6 +250,7 @@ public final class FetchImage: ObservableObject, Identifiable {
224250

225251
private func didFinishRequest(result: Result<ImageResponse, ImagePipeline.Error>, quality: ImageQuality) {
226252
task = nil
253+
currentlyLoadingImageQuality = nil
227254

228255
switch result {
229256
case let .success(response):

0 commit comments

Comments
 (0)