From a3136b02250bfcedfe98c93caabeb554069ad4e7 Mon Sep 17 00:00:00 2001 From: Yevgen Pogribnyi Date: Tue, 16 Jan 2018 20:08:29 +0200 Subject: [PATCH] [ASTraitCollection] Add missing properties to ASTraitCollection (#625) * [ASTraitCollection] Add missing properties to ASTraitCollection * ASTraitCollection now completely reflects UITraitCollection * Add ASContentSizeCategory enum that corresponds to UIContentSizeCategory and can be used inside a struct. * * Remove enum ASContentSizeCategory. * Use __unsafe_unretained UIContentSizeCategory instead of the enum. * Added ASPrimitiveTraitCollection lifetime test * Changes requested at code review: * Restore one of the ASTraitCollection constructors with a deprecation notice. * Clean up API by the separation of tvOS-specific interfaces. * Use [NSString -isEqualToString:] for ASPrimitiveContentSizeCategory equality tests for better readability. * Encapsulate fallback logic for UIContentSizeCategoryUnspecified. * Fix failing test --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 + CHANGELOG.md | 1 + Source/Details/ASTraitCollection.h | 100 +++++- Source/Details/ASTraitCollection.m | 405 +++++++++++++++++++--- Tests/ASCollectionViewTests.mm | 2 +- Tests/ASTraitCollectionTests.m | 34 ++ 6 files changed, 479 insertions(+), 67 deletions(-) create mode 100644 Tests/ASTraitCollectionTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index a04b064214..1cff2cac06 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -99,6 +99,7 @@ 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */; }; 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; }; + 4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */; }; 4E9127691F64157600499623 /* ASRunLoopQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */; }; 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; @@ -634,6 +635,7 @@ 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionReusableView.h; sourceTree = ""; }; 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionReusableView.m; sourceTree = ""; }; 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableViewTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASTraitCollectionTests.m; sourceTree = ""; }; 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableLayoutController.h; sourceTree = ""; }; @@ -1256,6 +1258,7 @@ 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */, 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */, + 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */, ); path = Tests; sourceTree = ""; @@ -2163,6 +2166,7 @@ buildActionMask = 2147483647; files = ( E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */, + 4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */, 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */, CC583AD71EF9BDC100134156 /* NSInvocation+ASTestHelpers.m in Sources */, CC051F1F1D7A286A006434CB /* ASCALayerTests.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index c884b80232..87165dc7e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) - [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719) - [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index 26714aa649..7f8ae3a476 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -17,6 +17,7 @@ #import + #import @class ASTraitCollection; @@ -27,14 +28,51 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN +#pragma mark - ASPrimitiveContentSizeCategory + +/** + * ASPrimitiveContentSizeCategory is a UIContentSizeCategory that can be used inside a struct. + * + * We need an unretained pointer because ARC can't manage struct memory. + * + * WARNING: DO NOT cast UIContentSizeCategory values to ASPrimitiveContentSizeCategory directly. + * Use ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory) instead. + * This is because we make some assumptions about the lifetime of the object it points to. + * Also note that cast from ASPrimitiveContentSizeCategory to UIContentSizeCategory is always safe. + */ +typedef __unsafe_unretained UIContentSizeCategory ASPrimitiveContentSizeCategory; + +/** + * Safely casts from UIContentSizeCategory to ASPrimitiveContentSizeCategory. + * + * The UIKit documentation doesn't specify if we can receive a copy of the UIContentSizeCategory constant. While getting + * copies is fine with ARC, usage of unretained pointers requires us to ensure the lifetime of the object it points to. + * Manual retain&release of the UIContentSizeCategory object is not an option because it would require us to do that + * everywhere ASPrimitiveTraitCollection is used. This is error-prone and can lead to crashes and memory leaks. So, we + * explicitly limit possible values of ASPrimitiveContentSizeCategory to the predetermined set of global constants with + * known lifetime. + * + * @return a pointer to one of the UIContentSizeCategory constants. + */ +extern ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory); + #pragma mark - ASPrimitiveTraitCollection typedef struct ASPrimitiveTraitCollection { - CGFloat displayScale; UIUserInterfaceSizeClass horizontalSizeClass; - UIUserInterfaceIdiom userInterfaceIdiom; UIUserInterfaceSizeClass verticalSizeClass; + + CGFloat displayScale; + UIDisplayGamut displayGamut; + + UIUserInterfaceIdiom userInterfaceIdiom; UIForceTouchCapability forceTouchCapability; + UITraitEnvironmentLayoutDirection layoutDirection; +#if TARGET_OS_TV + UIUserInterfaceStyle userInterfaceStyle; +#endif + + ASPrimitiveContentSizeCategory preferredContentSizeCategory; CGSize containerSize; } ASPrimitiveTraitCollection; @@ -124,11 +162,21 @@ ASDISPLAYNODE_EXTERN_C_END AS_SUBCLASSING_RESTRICTED @interface ASTraitCollection : NSObject -@property (nonatomic, assign, readonly) CGFloat displayScale; @property (nonatomic, assign, readonly) UIUserInterfaceSizeClass horizontalSizeClass; -@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; @property (nonatomic, assign, readonly) UIUserInterfaceSizeClass verticalSizeClass; + +@property (nonatomic, assign, readonly) CGFloat displayScale; +@property (nonatomic, assign, readonly) UIDisplayGamut displayGamut; + +@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; @property (nonatomic, assign, readonly) UIForceTouchCapability forceTouchCapability; +@property (nonatomic, assign, readonly) UITraitEnvironmentLayoutDirection layoutDirection; +#if TARGET_OS_TV +@property (nonatomic, assign, readonly) UIUserInterfaceStyle userInterfaceStyle; +#endif + +@property (nonatomic, assign, readonly) UIContentSizeCategory preferredContentSizeCategory; + @property (nonatomic, assign, readonly) CGSize containerSize; + (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits; @@ -136,18 +184,48 @@ AS_SUBCLASSING_RESTRICTED + (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize; ++ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection + containerSize:(CGSize)windowSize + fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory; -+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize; - +#if TARGET_OS_TV ++ (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize; +#else ++ (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize; +#endif - (ASPrimitiveTraitCollection)primitiveTraitCollection; - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection; @end +@interface ASTraitCollection (Deprecated) + ++ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize + ASDISPLAYNODE_DEPRECATED_MSG("Use full version of this method instead."); + +@end + NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 570095ec41..dba756bafa 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -20,6 +20,60 @@ #import #import +#pragma mark - ASPrimitiveContentSizeCategory + +// UIContentSizeCategoryUnspecified is available only in iOS 10.0 and later. +// This is used for compatibility with older iOS versions. +ASDISPLAYNODE_INLINE UIContentSizeCategory AS_UIContentSizeCategoryUnspecified() { + if (AS_AVAILABLE_IOS(10)) { + return UIContentSizeCategoryUnspecified; + } else { + return @"_UICTContentSizeCategoryUnspecified"; + } +} + +ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory) { + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraSmall]) { + return UIContentSizeCategoryExtraSmall; + } + if ([sizeCategory isEqualToString:UIContentSizeCategorySmall]) { + return UIContentSizeCategorySmall; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryMedium]) { + return UIContentSizeCategoryMedium; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryLarge]) { + return UIContentSizeCategoryLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraLarge]) { + return UIContentSizeCategoryExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraExtraLarge]) { + return UIContentSizeCategoryExtraExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) { + return UIContentSizeCategoryExtraExtraExtraLarge; + } + + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityMedium]) { + return UIContentSizeCategoryAccessibilityMedium; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityLarge]) { + return UIContentSizeCategoryAccessibilityLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraExtraExtraLarge; + } + + return AS_UIContentSizeCategoryUnspecified(); +} + #pragma mark - ASPrimitiveTraitCollection extern void ASTraitCollectionPropagateDown(id element, ASPrimitiveTraitCollection traitCollection) { @@ -32,36 +86,57 @@ extern void ASTraitCollectionPropagateDown(id element, ASPrimit } } -ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() -{ +ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() { return (ASPrimitiveTraitCollection) { // Default values can be defined in here + .displayGamut = UIDisplayGamutUnspecified, .userInterfaceIdiom = UIUserInterfaceIdiomUnspecified, + .layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified, + .preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(AS_UIContentSizeCategoryUnspecified()), .containerSize = CGSizeZero, }; } -ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) -{ +ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) { ASPrimitiveTraitCollection environmentTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); - environmentTraitCollection.displayScale = traitCollection.displayScale; environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; + environmentTraitCollection.displayScale = traitCollection.displayScale; environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; - if (AS_AVAILABLE_IOS(9)) { - environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + if (AS_AVAILABLE_IOS(10)) { + environmentTraitCollection.displayGamut = traitCollection.displayGamut; + environmentTraitCollection.layoutDirection = traitCollection.layoutDirection; + + // preferredContentSizeCategory is also available on older iOS versions, but only via UIApplication class. + // It should be noted that [UIApplication sharedApplication] is unavailable because Texture is built with only extension-safe API. + environmentTraitCollection.preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(traitCollection.preferredContentSizeCategory); + + #if TARGET_OS_TV + environmentTraitCollection.userInterfaceStyle = traitCollection.userInterfaceStyle; + #endif } return environmentTraitCollection; } -BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) -{ +BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) { + UIContentSizeCategory leftSizeCategory = (UIContentSizeCategory)lhs.preferredContentSizeCategory; + UIContentSizeCategory rightSizeCategory = (UIContentSizeCategory)rhs.preferredContentSizeCategory; + return lhs.verticalSizeClass == rhs.verticalSizeClass && lhs.horizontalSizeClass == rhs.horizontalSizeClass && lhs.displayScale == rhs.displayScale && + lhs.displayGamut == rhs.displayGamut && lhs.userInterfaceIdiom == rhs.userInterfaceIdiom && lhs.forceTouchCapability == rhs.forceTouchCapability && + lhs.layoutDirection == rhs.layoutDirection && + #if TARGET_OS_TV + lhs.userInterfaceStyle == rhs.userInterfaceStyle && + #endif + + [leftSizeCategory isEqualToString:rightSizeCategory] && // Simple pointer comparison should be sufficient here + CGSizeEqualToSize(lhs.containerSize, rhs.containerSize); } @@ -105,14 +180,58 @@ ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceSizeClass(UIUserInt } } -NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) -{ +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIDisplayGamut(UIDisplayGamut displayGamut) { + switch (displayGamut) { + case UIDisplayGamutSRGB: + return @"sRGB"; + case UIDisplayGamutP3: + return @"P3"; + default: + return @"Unspecified"; + } +} + +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUITraitEnvironmentLayoutDirection(UITraitEnvironmentLayoutDirection layoutDirection) { + switch (layoutDirection) { + case UITraitEnvironmentLayoutDirectionLeftToRight: + return @"LeftToRight"; + case UITraitEnvironmentLayoutDirectionRightToLeft: + return @"RightToLeft"; + default: + return @"Unspecified"; + } +} + +#if TARGET_OS_TV +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceStyle(UIUserInterfaceStyle userInterfaceStyle) { + switch (userInterfaceStyle) { + case UIUserInterfaceStyleLight: + return @"Light"; + case UIUserInterfaceStyleDark: + return @"Dark"; + default: + return @"Unspecified"; + } +} +#endif + +NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) { NSMutableArray *props = [NSMutableArray array]; - [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; - [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; - [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; [props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }]; + [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; + [props addObject:@{ @"displayScale": [NSString stringWithFormat: @"%.0lf", (double)traits.displayScale] }]; + [props addObject:@{ @"displayGamut": AS_NSStringFromUIDisplayGamut(traits.displayGamut) }]; + [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; [props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }]; + [props addObject:@{ @"layoutDirection": AS_NSStringFromUITraitEnvironmentLayoutDirection(traits.layoutDirection) }]; + #if TARGET_OS_TV + [props addObject:@{ @"userInterfaceStyle": AS_NSStringFromUIUserInterfaceStyle(traits.userInterfaceStyle) }]; + #endif + [props addObject:@{ @"preferredContentSizeCategory": (UIContentSizeCategory)traits.preferredContentSizeCategory }]; + [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; return ASObjectDescriptionMakeWithoutObject(props); } @@ -120,69 +239,238 @@ NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection trai @implementation ASTraitCollection -- (instancetype)initWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize +#if TARGET_OS_TV + +- (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize { self = [super init]; if (self) { - _displayScale = displayScale; - _userInterfaceIdiom = userInterfaceIdiom; _horizontalSizeClass = horizontalSizeClass; _verticalSizeClass = verticalSizeClass; + _displayScale = displayScale; + _displayGamut = displayGamut; + _userInterfaceIdiom = userInterfaceIdiom; _forceTouchCapability = forceTouchCapability; + _layoutDirection = layoutDirection; + _userInterfaceStyle = userInterfaceStyle; + _preferredContentSizeCategory = preferredContentSizeCategory; _containerSize = windowSize; } return self; } -+ (instancetype)traitCollectionWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize ++ (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize { - return [[self alloc] initWithDisplayScale:displayScale - userInterfaceIdiom:userInterfaceIdiom - horizontalSizeClass:horizontalSizeClass - verticalSizeClass:verticalSizeClass - forceTouchCapability:forceTouchCapability - containerSize:windowSize]; + return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:displayGamut + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:layoutDirection + userInterfaceStyle:userIntefaceStyle + preferredContentSizeCategory:preferredContentSizeCategory + containerSize:windowSize]; +} + +#else + +- (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize +{ + self = [super init]; + if (self) { + _horizontalSizeClass = horizontalSizeClass; + _verticalSizeClass = verticalSizeClass; + _displayScale = displayScale; + _displayGamut = displayGamut; + _userInterfaceIdiom = userInterfaceIdiom; + _forceTouchCapability = forceTouchCapability; + _layoutDirection = layoutDirection; + _preferredContentSizeCategory = preferredContentSizeCategory; + _containerSize = windowSize; + } + return self; +} + ++ (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize +{ + return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:displayGamut + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:layoutDirection + preferredContentSizeCategory:preferredContentSizeCategory + containerSize:windowSize]; +} + +#endif + ++ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize +{ +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + userInterfaceStyle:UIUserInterfaceStyleUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:windowSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:windowSize]; +#endif } + (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits { - return [self traitCollectionWithDisplayScale:traits.displayScale - userInterfaceIdiom:traits.userInterfaceIdiom - horizontalSizeClass:traits.horizontalSizeClass - verticalSizeClass:traits.verticalSizeClass - forceTouchCapability:traits.forceTouchCapability - containerSize:traits.containerSize]; +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + displayScale:traits.displayScale + displayGamut:traits.displayGamut + userInterfaceIdiom:traits.userInterfaceIdiom + forceTouchCapability:traits.forceTouchCapability + layoutDirection:traits.layoutDirection + userInterfaceStyle:traits.userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory + containerSize:traits.containerSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + displayScale:traits.displayScale + displayGamut:traits.displayGamut + userInterfaceIdiom:traits.userInterfaceIdiom + forceTouchCapability:traits.forceTouchCapability + layoutDirection:traits.layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory + containerSize:traits.containerSize]; +#endif } + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - containerSize:(CGSize)windowSize + containerSize:(CGSize)windowSize { - return [self traitCollectionWithDisplayScale:traitCollection.displayScale - userInterfaceIdiom:traitCollection.userInterfaceIdiom - horizontalSizeClass:traitCollection.horizontalSizeClass - verticalSizeClass:traitCollection.verticalSizeClass - forceTouchCapability:traitCollection.forceTouchCapability - containerSize:windowSize]; + return [self traitCollectionWithUITraitCollection:traitCollection + containerSize:windowSize + fallbackContentSizeCategory:AS_UIContentSizeCategoryUnspecified()]; +} + + ++ (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection + containerSize:(CGSize)windowSize + fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory +{ + UIDisplayGamut displayGamut; + UITraitEnvironmentLayoutDirection layoutDirection; + UIContentSizeCategory sizeCategory; + #if TARGET_OS_TV + UIUserInterfaceStyle userInterfaceStyle; + #endif + if (AS_AVAILABLE_IOS(10)) { + displayGamut = traitCollection.displayGamut; + layoutDirection = traitCollection.layoutDirection; + sizeCategory = traitCollection.preferredContentSizeCategory; + #if TARGET_OS_TV + userInterfaceStyle = traitCollection.userInterfaceStyle; + #endif + } else { + displayGamut = UIDisplayGamutUnspecified; + layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified; + sizeCategory = fallbackContentSizeCategory; + #if TARGET_OS_TV + userInterfaceStyle = UIUserInterfaceStyleUnspecified; + #endif + } + +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass + verticalSizeClass:traitCollection.verticalSizeClass + displayScale:traitCollection.displayScale + displayGamut:displayGamut + userInterfaceIdiom:traitCollection.userInterfaceIdiom + forceTouchCapability:traitCollection.forceTouchCapability + layoutDirection:layoutDirection + userInterfaceStyle:userInterfaceStyle + preferredContentSizeCategory:sizeCategory + containerSize:windowSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass + verticalSizeClass:traitCollection.verticalSizeClass + displayScale:traitCollection.displayScale + displayGamut:displayGamut + userInterfaceIdiom:traitCollection.userInterfaceIdiom + forceTouchCapability:traitCollection.forceTouchCapability + layoutDirection:layoutDirection + preferredContentSizeCategory:sizeCategory + containerSize:windowSize]; +#endif } - (ASPrimitiveTraitCollection)primitiveTraitCollection { return (ASPrimitiveTraitCollection) { - .displayScale = self.displayScale, .horizontalSizeClass = self.horizontalSizeClass, - .userInterfaceIdiom = self.userInterfaceIdiom, .verticalSizeClass = self.verticalSizeClass, + .displayScale = self.displayScale, + .displayGamut = self.displayGamut, + .userInterfaceIdiom = self.userInterfaceIdiom, .forceTouchCapability = self.forceTouchCapability, + .layoutDirection = self.layoutDirection, + #if TARGET_OS_TV + .userInterfaceStyle = self.userInterfaceStyle, + #endif + .preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(self.preferredContentSizeCategory), .containerSize = self.containerSize, }; } @@ -193,12 +481,19 @@ NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection trai return YES; } - return self.displayScale == traitCollection.displayScale && - self.horizontalSizeClass == traitCollection.horizontalSizeClass && - self.verticalSizeClass == traitCollection.verticalSizeClass && - self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && - CGSizeEqualToSize(self.containerSize, traitCollection.containerSize) && - self.forceTouchCapability == traitCollection.forceTouchCapability; + return + self.horizontalSizeClass == traitCollection.horizontalSizeClass && + self.verticalSizeClass == traitCollection.verticalSizeClass && + self.displayScale == traitCollection.displayScale && + self.displayGamut == traitCollection.displayGamut && + self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && + self.forceTouchCapability == traitCollection.forceTouchCapability && + self.layoutDirection == traitCollection.layoutDirection && + #if TARGET_OS_TV + self.userInterfaceStyle == traitCollection.userInterfaceStyle && + #endif + [self.preferredContentSizeCategory isEqualToString:traitCollection.preferredContentSizeCategory] && + CGSizeEqualToSize(self.containerSize, traitCollection.containerSize); } @end diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 06c6fcd890..0391e992da 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -1097,7 +1097,7 @@ [window layoutIfNeeded]; // The initial reload is async, changing the trait collection here should be "mid-update" - ASPrimitiveTraitCollection traitCollection; + ASPrimitiveTraitCollection traitCollection = ASPrimitiveTraitCollectionMakeDefault(); traitCollection.displayScale = cn.primitiveTraitCollection.displayScale + 1; // Just a dummy change traitCollection.containerSize = screenBounds.size; cn.primitiveTraitCollection = traitCollection; diff --git a/Tests/ASTraitCollectionTests.m b/Tests/ASTraitCollectionTests.m new file mode 100644 index 0000000000..aa106d75ec --- /dev/null +++ b/Tests/ASTraitCollectionTests.m @@ -0,0 +1,34 @@ +// +// ASTraitCollectionTests.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +@interface ASTraitCollectionTests : XCTestCase + +@end + +@implementation ASTraitCollectionTests + +- (void)testPrimitiveContentSizeCategoryLifetime +{ + ASPrimitiveContentSizeCategory primitiveContentSize; + @autoreleasepool { + // Make sure the compiler won't optimize string alloc/dealloc + NSString *contentSizeCategory = [NSString stringWithCString:"UICTContentSizeCategoryL" encoding:NSUTF8StringEncoding]; + primitiveContentSize = ASPrimitiveContentSizeCategoryMake(contentSizeCategory); + } + + XCTAssertEqual(primitiveContentSize, UIContentSizeCategoryLarge); +} + +@end