Skip to content

Commit 7891347

Browse files
authored
Merge pull request #77 from SDWebImage/bugfix_onSuccess_when_memory_cache_hit
Fix the issue that WebImage's onSuccess does not get called when memory cache hit for new created View Struct
2 parents 4336985 + f56c39a commit 7891347

File tree

6 files changed

+60
-26
lines changed

6 files changed

+60
-26
lines changed

SDWebImageSwiftUI.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
321C1D6B23DEDB98009CF62A /* WebImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3211F84F23DE98E300FC757F /* WebImageTests.swift */; };
2929
321C1D6C23DEDB98009CF62A /* AnimatedImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3211F84623DE984D00FC757F /* AnimatedImageTests.swift */; };
3030
321C1D6D23DEDB98009CF62A /* WebImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3211F84F23DE98E300FC757F /* WebImageTests.swift */; };
31+
322E0F4823E57F09006836DC /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0F4723E57F09006836DC /* TestUtils.swift */; };
32+
322E0F4923E57F09006836DC /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0F4723E57F09006836DC /* TestUtils.swift */; };
33+
322E0F4A23E57F09006836DC /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0F4723E57F09006836DC /* TestUtils.swift */; };
3134
326B84822363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
3235
326B84832363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
3336
326B84842363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
@@ -160,6 +163,7 @@
160163
3211F85423DE9D2700FC757F /* Images.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Images.bundle; sourceTree = "<group>"; };
161164
321C1D3B23DEC17D009CF62A /* SDWebImageSwiftUITests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SDWebImageSwiftUITests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
162165
321C1D4A23DEC185009CF62A /* SDWebImageSwiftUITests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SDWebImageSwiftUITests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
166+
322E0F4723E57F09006836DC /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = "<group>"; };
163167
326B84812363350C0011BDFB /* Indicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Indicator.swift; sourceTree = "<group>"; };
164168
326B8486236335110011BDFB /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
165169
326B848B236335400011BDFB /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = "<group>"; };
@@ -259,6 +263,7 @@
259263
3211F84623DE984D00FC757F /* AnimatedImageTests.swift */,
260264
3211F84F23DE98E300FC757F /* WebImageTests.swift */,
261265
32BD9C4623E03B08008D5F6A /* IndicatorTests.swift */,
266+
322E0F4723E57F09006836DC /* TestUtils.swift */,
262267
);
263268
path = Tests;
264269
sourceTree = "<group>";
@@ -711,6 +716,7 @@
711716
3211F85023DE98E300FC757F /* WebImageTests.swift in Sources */,
712717
32BD9C4723E03B08008D5F6A /* IndicatorTests.swift in Sources */,
713718
3211F84723DE984D00FC757F /* AnimatedImageTests.swift in Sources */,
719+
322E0F4823E57F09006836DC /* TestUtils.swift in Sources */,
714720
);
715721
runOnlyForDeploymentPostprocessing = 0;
716722
};
@@ -721,6 +727,7 @@
721727
321C1D6B23DEDB98009CF62A /* WebImageTests.swift in Sources */,
722728
32BD9C4823E03B08008D5F6A /* IndicatorTests.swift in Sources */,
723729
321C1D6A23DEDB98009CF62A /* AnimatedImageTests.swift in Sources */,
730+
322E0F4923E57F09006836DC /* TestUtils.swift in Sources */,
724731
);
725732
runOnlyForDeploymentPostprocessing = 0;
726733
};
@@ -731,6 +738,7 @@
731738
321C1D6D23DEDB98009CF62A /* WebImageTests.swift in Sources */,
732739
32BD9C4923E03B08008D5F6A /* IndicatorTests.swift in Sources */,
733740
321C1D6C23DEDB98009CF62A /* AnimatedImageTests.swift in Sources */,
741+
322E0F4A23E57F09006836DC /* TestUtils.swift in Sources */,
734742
);
735743
runOnlyForDeploymentPostprocessing = 0;
736744
};

SDWebImageSwiftUI/Classes/ImageManager.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class ImageManager : ObservableObject {
1919
weak var currentOperation: SDWebImageOperation? = nil
2020
var isSuccess: Bool = false // true means request for this URL is ended forever, load() do nothing
2121
var isIncremental: Bool = false // true means during incremental loading
22+
var isFirstLoad: Bool = true // false after first call `load()`
2223

2324
var url: URL?
2425
var options: SDWebImageOptions
@@ -39,6 +40,7 @@ class ImageManager : ObservableObject {
3940
}
4041

4142
func load() {
43+
isFirstLoad = false
4244
if currentOperation != nil {
4345
return
4446
}

SDWebImageSwiftUI/Classes/WebImage.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,16 @@ public struct WebImage : View {
5858
}
5959
}
6060
self.imageManager = ImageManager(url: url, options: options, context: context)
61-
// load remote image here, SwiftUI sometimes will create a new View struct without calling `onAppear` (like enter EditMode) :)
62-
// this can ensure we load the image, SDWebImage take care of the duplicated query
63-
self.imageManager.load()
6461
}
6562

6663
public var body: some View {
67-
Group {
64+
// load remote image when first called `body`, SwiftUI sometimes will create a new View struct without calling `onAppear` (like enter EditMode) :)
65+
// this can ensure we load the image, and display image synchronously when memory cache hit to avoid flashing
66+
// called once per struct, SDWebImage take care of the duplicated query
67+
if imageManager.isFirstLoad {
68+
imageManager.load()
69+
}
70+
return Group {
6871
if imageManager.image != nil {
6972
if isAnimating && !self.imageManager.isIncremental {
7073
if currentFrame != nil {

Tests/AnimatedImageTests.swift

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class AnimatedImageTests: XCTestCase {
3434

3535
func testAnimatedImageWithName() throws {
3636
let expectation = self.expectation(description: "AnimatedImage name initializer")
37-
let imageView = AnimatedImage(name: "TestImage.gif", bundle: testImageBundle())
37+
let imageView = AnimatedImage(name: "TestImage.gif", bundle: TestUtils.testImageBundle())
3838
let introspectView = imageView.introspectAnimatedImage { animatedImageView in
3939
if let animatedImage = animatedImageView.image as? SDAnimatedImage {
4040
XCTAssertEqual(animatedImage.animatedImageLoopCount, 0)
@@ -51,7 +51,7 @@ class AnimatedImageTests: XCTestCase {
5151

5252
func testAnimatedImageWithData() throws {
5353
let expectation = self.expectation(description: "AnimatedImage data initializer")
54-
let imageData = try XCTUnwrap(testImageData(name: "TestImageAnimated.apng"))
54+
let imageData = try XCTUnwrap(TestUtils.testImageData(name: "TestImageAnimated.apng"))
5555
let imageView = AnimatedImage(data: imageData)
5656
let introspectView = imageView.introspectAnimatedImage { animatedImageView in
5757
if let animatedImage = animatedImageView.image as? SDAnimatedImage {
@@ -91,7 +91,7 @@ class AnimatedImageTests: XCTestCase {
9191
let expectation = self.expectation(description: "AnimatedImage binding control")
9292
let binding = Binding<Bool>(wrappedValue: true)
9393
var context: AnimatedImage.Context?
94-
let imageView = AnimatedImage(name: "TestLoopCount.gif", bundle: testImageBundle(), isAnimating: binding)
94+
let imageView = AnimatedImage(name: "TestLoopCount.gif", bundle: TestUtils.testImageBundle(), isAnimating: binding)
9595
.onViewCreate { _, c in
9696
context = c
9797
}
@@ -171,22 +171,4 @@ class AnimatedImageTests: XCTestCase {
171171
self.waitForExpectations(timeout: 5, handler: nil)
172172
AnimatedImage.onViewDestroy()
173173
}
174-
175-
// MARK: Helper
176-
func testBundle() -> Bundle {
177-
Bundle(for: type(of: self))
178-
}
179-
180-
func testImageBundle() -> Bundle {
181-
let imagePath = (testBundle().resourcePath! as NSString).appendingPathComponent("Images.bundle")
182-
return Bundle(path: imagePath)!
183-
}
184-
185-
func testImageData(name: String) -> Data? {
186-
guard let url = testImageBundle().url(forResource: name, withExtension: nil) else {
187-
return nil
188-
}
189-
return try? Data(contentsOf: url)
190-
}
191-
192174
}

Tests/TestUtils.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import XCTest
2+
import SwiftUI
3+
4+
class TestUtils {
5+
static var testBundle = Bundle(for: TestUtils.self)
6+
7+
class func testImageBundle() -> Bundle {
8+
let imagePath = (testBundle.resourcePath! as NSString).appendingPathComponent("Images.bundle")
9+
return Bundle(path: imagePath)!
10+
}
11+
12+
class func testImageData(name: String) -> Data? {
13+
guard let url = testImageBundle().url(forResource: name, withExtension: nil) else {
14+
return nil
15+
}
16+
return try? Data(contentsOf: url)
17+
}
18+
}

Tests/WebImageTests.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class WebImageTests: XCTestCase {
1515
override func tearDown() {
1616
// Put teardown code here. This method is called after the invocation of each test method in the class.
1717
super.tearDown()
18-
SDImageCache.shared.clear(with: .all)
1918
}
2019

2120
func testWebImageWithStaticURL() throws {
@@ -107,4 +106,26 @@ class WebImageTests: XCTestCase {
107106
self.waitForExpectations(timeout: 5, handler: nil)
108107
}
109108

109+
func testWebImageOnSuccessWhenMemoryCacheHit() throws {
110+
let expectation = self.expectation(description: "WebImage onSuccess when memory hit")
111+
let imageUrl = URL(string: "https://foo.bar/buzz.png")
112+
let cacheKey = SDWebImageManager.shared.cacheKey(for: imageUrl)
113+
#if os(macOS)
114+
let testImage = TestUtils.testImageBundle().image(forResource: "TestImage")
115+
#else
116+
let testImage = UIImage(named: "TestImage", in: TestUtils.testImageBundle(), compatibleWith: nil)
117+
#endif
118+
SDImageCache.shared.storeImage(toMemory: testImage, forKey: cacheKey)
119+
let imageView = WebImage(url: imageUrl)
120+
let introspectView = imageView.onSuccess { image, cacheType in
121+
XCTAssert(cacheType == .memory)
122+
XCTAssertNotNil(image)
123+
XCTAssertEqual(image, testImage)
124+
expectation.fulfill()
125+
}
126+
_ = try introspectView.inspect(WebImage.self)
127+
ViewHosting.host(view: introspectView)
128+
self.waitForExpectations(timeout: 5, handler: nil)
129+
}
130+
110131
}

0 commit comments

Comments
 (0)