Skip to content

Commit 18637e4

Browse files
authored
Add support for dynamic placeholder images (#613)
Before this change, if you attempted to use a dynamic image for a placeholder (e.g. an image from an asset catalog that has both dark and light versions) you would get unpredictable results. This is because PINAnimatedImageView is drawing the image directly to the layer without consulting the view's current trait collection. So which version of the image you get depends entirely on what the global `UITraitCollection.currentTraitCollection` is. According to Apple, "UIKit updates the value of this property before calling several well-known methods of UIView, UIViewController, and UIPresentationController." Though they do not say what those methods are, experimentation concludes that "displayLayer:" is not one of those. To solve this, we need to make sure that when we generate the CGImage, we are explicit about using the view's trait collection so that we get the correct image.
1 parent 6c018cc commit 18637e4

File tree

7 files changed

+67
-0
lines changed

7 files changed

+67
-0
lines changed

PINRemoteImage.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@
109109
A7343C5B228993D100972894 /* NSHTTPURLResponse+MaxAge.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD28EAE81695DDF84BB76B8 /* NSHTTPURLResponse+MaxAge.m */; };
110110
A7343C60228993F400972894 /* NSHTTPURLResponse+MaxAge.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD28EAE81695DDF84BB76B8 /* NSHTTPURLResponse+MaxAge.m */; };
111111
ACD28AB87FABF6BA3B9BF4E4 /* NSDate+PINCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD28D963D79EEC14EE071CE /* NSDate+PINCacheTests.m */; };
112+
B5BB4023274DB69F0042AE1D /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5BB4022274DB69F0042AE1D /* Media.xcassets */; };
113+
B5BB4024274DB69F0042AE1D /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5BB4022274DB69F0042AE1D /* Media.xcassets */; };
112114
D93A340224182D46005EB15E /* TestAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D93A340124182D46005EB15E /* TestAnimatedImage.m */; };
113115
D93A340324182D46005EB15E /* TestAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D93A340124182D46005EB15E /* TestAnimatedImage.m */; };
114116
F1B918FF1BCF23C900710963 /* PINRemoteImageCategoryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F1B918DC1BCF23C800710963 /* PINRemoteImageCategoryManager.m */; };
@@ -268,6 +270,7 @@
268270
ACD28A0374E664CFF0BB3297 /* NSHTTPURLResponse+MaxAge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSHTTPURLResponse+MaxAge.h"; sourceTree = "<group>"; };
269271
ACD28D963D79EEC14EE071CE /* NSDate+PINCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+PINCacheTests.m"; sourceTree = "<group>"; };
270272
ACD28EAE81695DDF84BB76B8 /* NSHTTPURLResponse+MaxAge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSHTTPURLResponse+MaxAge.m"; sourceTree = "<group>"; };
273+
B5BB4022274DB69F0042AE1D /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
271274
D93A340024182D46005EB15E /* TestAnimatedImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestAnimatedImage.h; sourceTree = "<group>"; };
272275
D93A340124182D46005EB15E /* TestAnimatedImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestAnimatedImage.m; sourceTree = "<group>"; };
273276
F1B918D11BCF239200710963 /* PINRemoteImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PINRemoteImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -381,6 +384,7 @@
381384
683128F41F95045200D5B4A8 /* PINAnimatedImage+PINAnimatedImageTesting.m */,
382385
ACD288C6E6B13E6DA226D252 /* NSDate+PINCacheTests.h */,
383386
ACD28D963D79EEC14EE071CE /* NSDate+PINCacheTests.m */,
387+
B5BB4022274DB69F0042AE1D /* Media.xcassets */,
384388
);
385389
path = Tests;
386390
sourceTree = "<group>";
@@ -803,13 +807,15 @@
803807
isa = PBXResourcesBuildPhase;
804808
buildActionMask = 2147483647;
805809
files = (
810+
B5BB4024274DB69F0042AE1D /* Media.xcassets in Resources */,
806811
);
807812
runOnlyForDeploymentPostprocessing = 0;
808813
};
809814
68A0FC171E523434000B552D /* Resources */ = {
810815
isa = PBXResourcesBuildPhase;
811816
buildActionMask = 2147483647;
812817
files = (
818+
B5BB4023274DB69F0042AE1D /* Media.xcassets in Resources */,
813819
0E71BE2A22E94C7200FC5B99 /* fireworks.gif in Resources */,
814820
);
815821
runOnlyForDeploymentPostprocessing = 0;

Source/Classes/AnimatedImages/PINAnimatedImageView.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ - (CGImageRef)imageRef
271271
if (_animatedImage) {
272272
return _frameImage;
273273
} else if ((underlyingImage = [super image])) {
274+
#if PIN_TARGET_IOS
275+
if (@available(iOS 13.0, tvOS 10.0, *)) {
276+
if (underlyingImage.imageAsset != nil) {
277+
underlyingImage = [underlyingImage.imageAsset imageWithTraitCollection:self.traitCollection];
278+
}
279+
}
280+
#endif
274281
return (CGImageRef)CFAutorelease(CFRetain([underlyingImage CGImage]));
275282
}
276283
return nil;

Tests/Media.xcassets/Contents.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "Light.png",
5+
"idiom" : "universal"
6+
},
7+
{
8+
"appearances" : [
9+
{
10+
"appearance" : "luminosity",
11+
"value" : "dark"
12+
}
13+
],
14+
"filename" : "Dark.png",
15+
"idiom" : "universal"
16+
}
17+
],
18+
"info" : {
19+
"author" : "xcode",
20+
"version" : 1
21+
}
22+
}
21.4 KB
Loading
13.1 KB
Loading

Tests/PINAnimatedImageTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,30 @@ class PINAnimatedImageTests: XCTestCase, PINRemoteImageManagerAlternateRepresent
260260
index = gifAnimatedImageView.frameIndex(atPlayHeadPosition: 0.41)
261261
XCTAssert(index == 4)
262262
}
263+
264+
@available(iOS 13.0, tvOS 10.0, *)
265+
func testDynamicPlaceholderImages() {
266+
let bundle = Bundle(for: type(of: self))
267+
let dynamicImage = UIImage(named: "DynamicImage", in: bundle, with: nil)
268+
XCTAssertNotNil(dynamicImage, "Unable to read image")
269+
270+
let lightTraitCollection = UITraitCollection(traitsFrom: [.current, .init(userInterfaceStyle: .light)])
271+
let darkTraitCollection = UITraitCollection(traitsFrom: [.current, .init(userInterfaceStyle: .dark)])
272+
273+
let lightImage = dynamicImage?.imageAsset?.image(with: lightTraitCollection).cgImage
274+
let darkImage = dynamicImage?.imageAsset?.image(with: darkTraitCollection).cgImage
275+
276+
let imageView = PINAnimatedImageView()
277+
imageView.image = dynamicImage
278+
279+
let layer = CALayer()
280+
281+
imageView.overrideUserInterfaceStyle = .light
282+
imageView.display(layer)
283+
XCTAssert(lightImage === (layer.contents as! CGImage), "Placeholder image should be using light mode version")
284+
285+
imageView.overrideUserInterfaceStyle = .dark
286+
imageView.display(layer)
287+
XCTAssert(darkImage === (layer.contents as! CGImage), "Placeholder image should be using dark mode version")
288+
}
263289
}

0 commit comments

Comments
 (0)