diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index cd1e5da8e..9e719c0b5 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -364,6 +364,7 @@ CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.mm */; }; CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */; }; CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.mm */; }; + CC4E8DAF232C2883007C3182 /* ASGraphicsContextTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC4E8DAE232C2882007C3182 /* ASGraphicsContextTests.mm */; }; CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = CC54A81B1D70077A00296A24 /* ASDispatch.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC54A81E1D7008B300296A24 /* ASDispatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC54A81D1D7008B300296A24 /* ASDispatchTests.mm */; }; CC55A70D1E529FA200594372 /* UIResponder+AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -434,7 +435,7 @@ CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm */; }; CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; }; CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.mm */; }; - CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; }; + CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.mm */; }; CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm */; }; CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.mm */; }; @@ -892,6 +893,7 @@ CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTableViewThrashTests.mm; sourceTree = ""; }; CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+ASHelpers.h"; sourceTree = ""; }; CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSIndexSet+ASHelpers.mm"; sourceTree = ""; }; + CC4E8DAE232C2882007C3182 /* ASGraphicsContextTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASGraphicsContextTests.mm; sourceTree = ""; }; CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASTableView+Undeprecated.h"; sourceTree = ""; }; CC54A81B1D70077A00296A24 /* ASDispatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASDispatch.h; sourceTree = ""; }; CC54A81D1D7008B300296A24 /* ASDispatchTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDispatchTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -1335,6 +1337,7 @@ 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.mm */, F3F698D1211CAD4600800CB1 /* ASDisplayViewAccessibilityTests.mm */, 697B31591CFE4B410049936F /* ASEditableTextNodeTests.mm */, + CC4E8DAE232C2882007C3182 /* ASGraphicsContextTests.mm */, 471D04B0224CB98600649215 /* ASImageNodeBackingSizeTests.mm */, 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.mm */, D99F9157232990F30083CC8E /* ASImageNodeTests.m */, @@ -2324,6 +2327,7 @@ 4E9127691F64157600499623 /* ASRunLoopQueueTests.mm in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.mm in Sources */, CC54A81E1D7008B300296A24 /* ASDispatchTests.mm in Sources */, + CC4E8DAF232C2883007C3182 /* ASGraphicsContextTests.mm in Sources */, F3F698D2211CAD4600800CB1 /* ASDisplayViewAccessibilityTests.mm in Sources */, CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.mm in Sources */, 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.mm in Sources */, diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index eaab7784f..19d823abf 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -130,3 +130,4 @@ #import #import #import +#import diff --git a/Source/Details/ASGraphicsContext.h b/Source/Details/ASGraphicsContext.h index 8d08b600c..da1349cf0 100644 --- a/Source/Details/ASGraphicsContext.h +++ b/Source/Details/ASGraphicsContext.h @@ -9,6 +9,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -27,6 +28,21 @@ NS_ASSUME_NONNULL_BEGIN * * @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext. */ -AS_EXTERN UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE _Nullable isCancelled, void (NS_NOESCAPE ^work)()); +AS_EXTERN UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE _Nullable isCancelled, void (NS_NOESCAPE ^work)(void)); + +/** +* A wrapper for the UIKit drawing APIs. +* +* @param traitCollection Trait collection. The `work` block will be executed with this trait collection, so it will affect dynamic colors, etc. +* @param size The size of the context. +* @param opaque Whether the context should be opaque or not. +* @param scale The scale of the context. 0 uses main screen scale. +* @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its +* preferred renderer format if we are using UIGraphicsImageRenderer. +* @param work A block, wherein the current UIGraphics context is set based on the arguments. +* +* @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext. +*/ +AS_EXTERN UIImage *ASGraphicsCreateImageWithTraitCollectionAndOptions(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, void (NS_NOESCAPE ^work)(void)); NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASGraphicsContext.mm b/Source/Details/ASGraphicsContext.mm index 8591f5675..89882681c 100644 --- a/Source/Details/ASGraphicsContext.mm +++ b/Source/Details/ASGraphicsContext.mm @@ -10,6 +10,7 @@ #import #import #import +#import NS_AVAILABLE_IOS(10) NS_INLINE void ASConfigureExtendedRange(UIGraphicsImageRendererFormat *format) @@ -94,3 +95,20 @@ NS_INLINE void ASConfigureExtendedRange(UIGraphicsImageRendererFormat *format) UIGraphicsEndImageContext(); return image; } + +UIImage *ASGraphicsCreateImageWithTraitCollectionAndOptions(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * sourceImage, void (NS_NOESCAPE ^work)()) { +#if AS_AT_LEAST_IOS13 + if (@available(iOS 13.0, *)) { + UITraitCollection *uiTraitCollection = ASPrimitiveTraitCollectionToUITraitCollection(traitCollection); + return ASGraphicsCreateImageWithOptions(size, opaque, scale, sourceImage, nil, ^{ + [uiTraitCollection performAsCurrentTraitCollection:^{ + work(); + }]; + }); + } else { + return ASGraphicsCreateImageWithOptions(size, opaque, scale, sourceImage, nil, work); + } +#else + return ASGraphicsCreateImageWithOptions(size, opaque, scale, sourceImage, nil, work); +#endif +} diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index a92205477..6378361dc 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -60,6 +60,11 @@ AS_EXTERN ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault(void) */ AS_EXTERN ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection); +/** + * Creates a UITraitCollection from a given ASPrimitiveTraitCollection. + */ +AS_EXTERN UITraitCollection * ASPrimitiveTraitCollectionToUITraitCollection(ASPrimitiveTraitCollection traitCollection); + /** * Compares two ASPrimitiveTraitCollection to determine if they are the same. diff --git a/Source/Details/ASTraitCollection.mm b/Source/Details/ASTraitCollection.mm index 13c180cb8..db37434bb 100644 --- a/Source/Details/ASTraitCollection.mm +++ b/Source/Details/ASTraitCollection.mm @@ -64,6 +64,28 @@ ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITra return environmentTraitCollection; } +AS_EXTERN UITraitCollection * ASPrimitiveTraitCollectionToUITraitCollection(ASPrimitiveTraitCollection traitCollection) { + NSMutableArray *collections = [[NSMutableArray alloc] initWithArray:@[ + [UITraitCollection traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass], + [UITraitCollection traitCollectionWithVerticalSizeClass:traitCollection.verticalSizeClass], + [UITraitCollection traitCollectionWithDisplayScale:traitCollection.displayScale], + [UITraitCollection traitCollectionWithUserInterfaceIdiom:traitCollection.userInterfaceIdiom], + [UITraitCollection traitCollectionWithForceTouchCapability:traitCollection.forceTouchCapability], + ]]; + + if (AS_AVAILABLE_IOS(10)) { + [collections addObject:[UITraitCollection traitCollectionWithDisplayGamut:traitCollection.displayGamut]]; + [collections addObject:[UITraitCollection traitCollectionWithLayoutDirection:traitCollection.layoutDirection]]; + [collections addObject:[UITraitCollection traitCollectionWithPreferredContentSizeCategory:traitCollection.preferredContentSizeCategory]]; + } + if (AS_AVAILABLE_IOS_TVOS(12, 10)) { + [collections addObject:[UITraitCollection traitCollectionWithUserInterfaceStyle:traitCollection.userInterfaceStyle]]; + } + + UITraitCollection *result = [UITraitCollection traitCollectionWithTraitsFromCollections:collections]; + return result; +} + BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) { return !memcmp(&lhs, &rhs, sizeof(ASPrimitiveTraitCollection)); } diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 7359aaddc..6660c2bbb 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -1152,6 +1152,31 @@ - (void)testTraitCollectionChangesMidUpdate } } +- (void)testASPrimitiveTraitCollectionToUITraitCollection { + ASPrimitiveTraitCollection collection = ASPrimitiveTraitCollectionMakeDefault(); + collection.displayGamut = UIDisplayGamutSRGB; + collection.displayScale = 11; + collection.forceTouchCapability = UIForceTouchCapabilityAvailable; + collection.horizontalSizeClass = UIUserInterfaceSizeClassRegular; + collection.layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft; + collection.preferredContentSizeCategory = UIContentSizeCategoryMedium; + collection.userInterfaceIdiom = UIUserInterfaceIdiomTV; + if (@available(iOS 12.0, *)) { + collection.userInterfaceStyle = UIUserInterfaceStyleDark; + } + collection.verticalSizeClass = UIUserInterfaceSizeClassRegular; + + // create `UITraitCollection` from `ASPrimitiveTraitCollection` + UITraitCollection *uiCollection = ASPrimitiveTraitCollectionToUITraitCollection(collection); + + // now convert it back to `ASPrimitiveTraitCollection` + ASPrimitiveTraitCollection newCollection = ASPrimitiveTraitCollectionFromUITraitCollection(uiCollection); + + // compare + XCTAssert(ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(collection, newCollection)); +} + + /** * This tests an issue where, since subnode insertions aren't applied until the UIKit layout pass, * which we trigger during the display phase, subnodes like network image nodes are not preloading diff --git a/Tests/ASGraphicsContextTests.mm b/Tests/ASGraphicsContextTests.mm new file mode 100644 index 000000000..35c24b42a --- /dev/null +++ b/Tests/ASGraphicsContextTests.mm @@ -0,0 +1,58 @@ +// +// ASGraphicsContextTests.mm +// Texture +// +// Copyright (c) Pinterest, Inc. All rights reserved. +// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 +// + + +#import +#import +#import +#import +#import + +@interface ASGraphicsContextTests : XCTestCase +@end + +@implementation ASGraphicsContextTests + +- (void)setUp +{ + [super setUp]; + ASConfiguration *config = [ASConfiguration new]; + config.experimentalFeatures = ASExperimentalDrawingGlobal; + [ASConfigurationManager test_resetWithConfiguration:config]; +} + + +#if AS_AT_LEAST_IOS13 +- (void)testTraitCollectionPassedToWork +{ + if (AS_AVAILABLE_IOS_TVOS(13, 13)) { + CGSize size = CGSize{.width=100, .height=100}; + + XCTestExpectation *expectationDark = [self expectationWithDescription:@"trait collection dark"]; + ASPrimitiveTraitCollection traitCollectionDark = ASPrimitiveTraitCollectionMakeDefault(); + traitCollectionDark.userInterfaceStyle = UIUserInterfaceStyleDark; + ASGraphicsCreateImageWithTraitCollectionAndOptions(traitCollectionDark, size, false, 0, nil, ^{ + UITraitCollection *currentTraitCollection = [UITraitCollection currentTraitCollection]; + XCTAssertEqual(currentTraitCollection.userInterfaceStyle, UIUserInterfaceStyleDark); + [expectationDark fulfill]; + }); + + XCTestExpectation *expectationLight = [self expectationWithDescription:@"trait collection light"]; + ASPrimitiveTraitCollection traitCollectionLight = ASPrimitiveTraitCollectionMakeDefault(); + traitCollectionLight.userInterfaceStyle = UIUserInterfaceStyleLight; + ASGraphicsCreateImageWithTraitCollectionAndOptions(traitCollectionLight, size, false, 0, nil, ^{ + UITraitCollection *currentTraitCollection = [UITraitCollection currentTraitCollection]; + XCTAssertEqual(currentTraitCollection.userInterfaceStyle, UIUserInterfaceStyleLight); + [expectationLight fulfill]; + }); + + [self waitForExpectations:@[expectationDark, expectationLight] timeout:1]; + } +} +#endif +@end