Skip to content

Commit

Permalink
Add support for dynamic placeholder images (#613)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bdolman committed Mar 2, 2022
1 parent 6c018cc commit 18637e4
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 0 deletions.
6 changes: 6 additions & 0 deletions PINRemoteImage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@
A7343C5B228993D100972894 /* NSHTTPURLResponse+MaxAge.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD28EAE81695DDF84BB76B8 /* NSHTTPURLResponse+MaxAge.m */; };
A7343C60228993F400972894 /* NSHTTPURLResponse+MaxAge.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD28EAE81695DDF84BB76B8 /* NSHTTPURLResponse+MaxAge.m */; };
ACD28AB87FABF6BA3B9BF4E4 /* NSDate+PINCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD28D963D79EEC14EE071CE /* NSDate+PINCacheTests.m */; };
B5BB4023274DB69F0042AE1D /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5BB4022274DB69F0042AE1D /* Media.xcassets */; };
B5BB4024274DB69F0042AE1D /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5BB4022274DB69F0042AE1D /* Media.xcassets */; };
D93A340224182D46005EB15E /* TestAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D93A340124182D46005EB15E /* TestAnimatedImage.m */; };
D93A340324182D46005EB15E /* TestAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D93A340124182D46005EB15E /* TestAnimatedImage.m */; };
F1B918FF1BCF23C900710963 /* PINRemoteImageCategoryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F1B918DC1BCF23C800710963 /* PINRemoteImageCategoryManager.m */; };
Expand Down Expand Up @@ -268,6 +270,7 @@
ACD28A0374E664CFF0BB3297 /* NSHTTPURLResponse+MaxAge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSHTTPURLResponse+MaxAge.h"; sourceTree = "<group>"; };
ACD28D963D79EEC14EE071CE /* NSDate+PINCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+PINCacheTests.m"; sourceTree = "<group>"; };
ACD28EAE81695DDF84BB76B8 /* NSHTTPURLResponse+MaxAge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSHTTPURLResponse+MaxAge.m"; sourceTree = "<group>"; };
B5BB4022274DB69F0042AE1D /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
D93A340024182D46005EB15E /* TestAnimatedImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestAnimatedImage.h; sourceTree = "<group>"; };
D93A340124182D46005EB15E /* TestAnimatedImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestAnimatedImage.m; sourceTree = "<group>"; };
F1B918D11BCF239200710963 /* PINRemoteImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PINRemoteImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -381,6 +384,7 @@
683128F41F95045200D5B4A8 /* PINAnimatedImage+PINAnimatedImageTesting.m */,
ACD288C6E6B13E6DA226D252 /* NSDate+PINCacheTests.h */,
ACD28D963D79EEC14EE071CE /* NSDate+PINCacheTests.m */,
B5BB4022274DB69F0042AE1D /* Media.xcassets */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -803,13 +807,15 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5BB4024274DB69F0042AE1D /* Media.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
68A0FC171E523434000B552D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5BB4023274DB69F0042AE1D /* Media.xcassets in Resources */,
0E71BE2A22E94C7200FC5B99 /* fireworks.gif in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
7 changes: 7 additions & 0 deletions Source/Classes/AnimatedImages/PINAnimatedImageView.m
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,13 @@ - (CGImageRef)imageRef
if (_animatedImage) {
return _frameImage;
} else if ((underlyingImage = [super image])) {
#if PIN_TARGET_IOS
if (@available(iOS 13.0, tvOS 10.0, *)) {
if (underlyingImage.imageAsset != nil) {
underlyingImage = [underlyingImage.imageAsset imageWithTraitCollection:self.traitCollection];
}
}
#endif
return (CGImageRef)CFAutorelease(CFRetain([underlyingImage CGImage]));
}
return nil;
Expand Down
6 changes: 6 additions & 0 deletions Tests/Media.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
22 changes: 22 additions & 0 deletions Tests/Media.xcassets/DynamicImage.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "Light.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions Tests/PINAnimatedImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,30 @@ class PINAnimatedImageTests: XCTestCase, PINRemoteImageManagerAlternateRepresent
index = gifAnimatedImageView.frameIndex(atPlayHeadPosition: 0.41)
XCTAssert(index == 4)
}

@available(iOS 13.0, tvOS 10.0, *)
func testDynamicPlaceholderImages() {
let bundle = Bundle(for: type(of: self))
let dynamicImage = UIImage(named: "DynamicImage", in: bundle, with: nil)
XCTAssertNotNil(dynamicImage, "Unable to read image")

let lightTraitCollection = UITraitCollection(traitsFrom: [.current, .init(userInterfaceStyle: .light)])
let darkTraitCollection = UITraitCollection(traitsFrom: [.current, .init(userInterfaceStyle: .dark)])

let lightImage = dynamicImage?.imageAsset?.image(with: lightTraitCollection).cgImage
let darkImage = dynamicImage?.imageAsset?.image(with: darkTraitCollection).cgImage

let imageView = PINAnimatedImageView()
imageView.image = dynamicImage

let layer = CALayer()

imageView.overrideUserInterfaceStyle = .light
imageView.display(layer)
XCTAssert(lightImage === (layer.contents as! CGImage), "Placeholder image should be using light mode version")

imageView.overrideUserInterfaceStyle = .dark
imageView.display(layer)
XCTAssert(darkImage === (layer.contents as! CGImage), "Placeholder image should be using dark mode version")
}
}

0 comments on commit 18637e4

Please sign in to comment.