diff --git a/Framework/Sources/UIColor+SRGAppearance.h b/Framework/Sources/UIColor+SRGAppearance.h index d4fc374..0103bbc 100644 --- a/Framework/Sources/UIColor+SRGAppearance.h +++ b/Framework/Sources/UIColor+SRGAppearance.h @@ -8,6 +8,12 @@ NS_ASSUME_NONNULL_BEGIN +/** + * Transformer between hexadecimal strings and colors. Convert valid hexadecimal strings with or without leading + * wildcards to colors. Conversely, produces hexadecimal strings from colors (always with leading wildcard): + */ +OBJC_EXPORT NSValueTransformer *SRGHexadecimalColorTransformer(void); + @interface UIColor (SRGAppearance) /** @@ -20,6 +26,12 @@ NS_ASSUME_NONNULL_BEGIN */ + (UIColor *)srg_blueColor; +/** + * Return the color matching a hexadecimal string (with or without leading wildcard), `nil` if the string does + * not correspond to a color. + */ ++ (nullable UIColor *)srg_colorFromHexadecimalString:(NSString *)hexadecimalString; + @end NS_ASSUME_NONNULL_END diff --git a/Framework/Sources/UIColor+SRGAppearance.m b/Framework/Sources/UIColor+SRGAppearance.m index 57178a3..55b3d02 100644 --- a/Framework/Sources/UIColor+SRGAppearance.m +++ b/Framework/Sources/UIColor+SRGAppearance.m @@ -6,16 +6,83 @@ #import "UIColor+SRGAppearance.h" +@interface SRGHexadecimalColorValueTransformer : NSValueTransformer + +@end + @implementation UIColor (SRGAppearance) + (UIColor *)srg_redColor { - return [UIColor colorWithRed:157.f / 255.f green:0.f / 255.f blue:24.f / 255.f alpha:1.f]; + return [UIColor srg_colorFromHexadecimalString:@"#9d0018"]; } + (UIColor *)srg_blueColor { - return [UIColor colorWithRed:15.f / 255.f green:90.f / 255.f blue:203.f / 255.f alpha:1.f]; + return [UIColor srg_colorFromHexadecimalString:@"#0f5acb"]; +} + ++ (UIColor *)srg_colorFromHexadecimalString:(NSString *)hexadecimalString +{ + return [SRGHexadecimalColorTransformer() transformedValue:hexadecimalString]; } @end + +@implementation SRGHexadecimalColorValueTransformer + +#pragma mark Overrides + ++ (Class)transformedValueClass +{ + return [UIColor class]; +} + ++ (BOOL)allowsReverseTransformation +{ + return YES; +} + +- (id)transformedValue:(id)value +{ + if (! [value isKindOfClass:[NSString class]]) { + return nil; + } + + NSScanner *scanner = [NSScanner scannerWithString:value]; + if ([value hasPrefix:@"#"]) { + [scanner setScanLocation:1]; + } + + unsigned rgbValue = 0; + if (! [scanner scanHexInt:&rgbValue]) { + return nil; + } + + CGFloat red = ((rgbValue & 0xFF0000) >> 16) / 255.f; + CGFloat green = ((rgbValue & 0x00FF00) >> 8) / 255.f; + CGFloat blue = (rgbValue & 0x0000FF) / 255.f; + return [UIColor colorWithRed:red green:green blue:blue alpha:1.f]; +} + +- (id)reverseTransformedValue:(id)value +{ + if (! [value isKindOfClass:[UIColor class]]) { + return nil; + } + + const CGFloat *components = CGColorGetComponents([value CGColor]); + return [NSString stringWithFormat:@"#%02lx%02lx%02lx", lroundf(components[0] * 255), lroundf(components[1] * 255), lroundf(components[2] * 255)]; +} + +@end + +NSValueTransformer *SRGHexadecimalColorTransformer(void) +{ + static NSValueTransformer *s_transformer; + static dispatch_once_t s_onceToken; + dispatch_once(&s_onceToken, ^{ + s_transformer = [[SRGHexadecimalColorValueTransformer alloc] init]; + }); + return s_transformer; +} diff --git a/Framework/Sources/UIFont+SRGAppearance.h b/Framework/Sources/UIFont+SRGAppearance.h index 98957b3..2529f9f 100644 --- a/Framework/Sources/UIFont+SRGAppearance.h +++ b/Framework/Sources/UIFont+SRGAppearance.h @@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN // Custom SRG SSR font text styles. The font size for the usual content size category (large) is provided as information. -typedef NSString * SRGAppearanceFontTextStyle NS_STRING_ENUM; +typedef NSString * SRGAppearanceFontTextStyle NS_TYPED_ENUM; OBJC_EXPORT SRGAppearanceFontTextStyle const SRGAppearanceFontTextStyleCaption; // 11 pts OBJC_EXPORT SRGAppearanceFontTextStyle const SRGAppearanceFontTextStyleSubtitle; // 13 pts diff --git a/SRGAppearance.xcodeproj/project.pbxproj b/SRGAppearance.xcodeproj/project.pbxproj index bea5852..7961151 100644 --- a/SRGAppearance.xcodeproj/project.pbxproj +++ b/SRGAppearance.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 6F131A7A1E8C1E17000D6BEC /* Sketch.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6F131A781E8C1E17000D6BEC /* Sketch.ttf */; }; 6F131A7C1E8C1EBC000D6BEC /* Venetian.otf in Resources */ = {isa = PBXBuildFile; fileRef = 6F131A7B1E8C1EBC000D6BEC /* Venetian.otf */; }; + 6F5543ED20ADA2960049ECB2 /* FontsTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F5543EC20ADA2950049ECB2 /* FontsTestCase.m */; }; 6F5769AE1EBCB38A00F931CA /* UIImage+SRGAppearance.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F5769AC1EBCB38A00F931CA /* UIImage+SRGAppearance.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6F5769AF1EBCB38A00F931CA /* UIImage+SRGAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F5769AD1EBCB38A00F931CA /* UIImage+SRGAppearance.m */; }; 6F5769B11EBCBE6100F931CA /* ImageTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F5769B01EBCBE6100F931CA /* ImageTestCase.m */; }; @@ -17,7 +18,7 @@ 6FA09D4B1D9E70ED00EDCA64 /* NSBundle+SRGAppearance.h in Headers */ = {isa = PBXBuildFile; fileRef = 6FA09D491D9E70ED00EDCA64 /* NSBundle+SRGAppearance.h */; }; 6FA09D4C1D9E70ED00EDCA64 /* NSBundle+SRGAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FA09D4A1D9E70ED00EDCA64 /* NSBundle+SRGAppearance.m */; }; 6FA09D6B1D9E85BC00EDCA64 /* SRGAppearance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FA09D361D9E6F8900EDCA64 /* SRGAppearance.framework */; }; - 6FA09D761D9E861600EDCA64 /* FontsTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FA09D741D9E861600EDCA64 /* FontsTestCase.m */; }; + 6FA09D761D9E861600EDCA64 /* ColorTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FA09D741D9E861600EDCA64 /* ColorTestCase.m */; }; 6FE08C531E8A818C00EC4716 /* UIFont+SRGAppearance.h in Headers */ = {isa = PBXBuildFile; fileRef = 6FE08C4F1E8A818C00EC4716 /* UIFont+SRGAppearance.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6FE08C541E8A818C00EC4716 /* UIFont+SRGAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FE08C501E8A818C00EC4716 /* UIFont+SRGAppearance.m */; }; 6FE08C551E8A818C00EC4716 /* UIFontDescriptor+SRGAppearance.h in Headers */ = {isa = PBXBuildFile; fileRef = 6FE08C511E8A818C00EC4716 /* UIFontDescriptor+SRGAppearance.h */; }; @@ -72,6 +73,7 @@ /* Begin PBXFileReference section */ 6F131A781E8C1E17000D6BEC /* Sketch.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Sketch.ttf; sourceTree = ""; }; 6F131A7B1E8C1EBC000D6BEC /* Venetian.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Venetian.otf; sourceTree = ""; }; + 6F5543EC20ADA2950049ECB2 /* FontsTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontsTestCase.m; path = Tests/Sources/FontsTestCase.m; sourceTree = SOURCE_ROOT; }; 6F5769AC1EBCB38A00F931CA /* UIImage+SRGAppearance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+SRGAppearance.h"; sourceTree = ""; }; 6F5769AD1EBCB38A00F931CA /* UIImage+SRGAppearance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+SRGAppearance.m"; sourceTree = ""; }; 6F5769B01EBCBE6100F931CA /* ImageTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ImageTestCase.m; path = Tests/Sources/ImageTestCase.m; sourceTree = SOURCE_ROOT; }; @@ -83,7 +85,7 @@ 6FA09D4A1D9E70ED00EDCA64 /* NSBundle+SRGAppearance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+SRGAppearance.m"; sourceTree = ""; }; 6FA09D661D9E85BC00EDCA64 /* SRGAppearance-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SRGAppearance-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 6FA09D721D9E861600EDCA64 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6FA09D741D9E861600EDCA64 /* FontsTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FontsTestCase.m; sourceTree = ""; }; + 6FA09D741D9E861600EDCA64 /* ColorTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ColorTestCase.m; sourceTree = ""; }; 6FE08C4F1E8A818C00EC4716 /* UIFont+SRGAppearance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+SRGAppearance.h"; sourceTree = ""; }; 6FE08C501E8A818C00EC4716 /* UIFont+SRGAppearance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+SRGAppearance.m"; sourceTree = ""; }; 6FE08C511E8A818C00EC4716 /* UIFontDescriptor+SRGAppearance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFontDescriptor+SRGAppearance.h"; sourceTree = ""; }; @@ -195,8 +197,9 @@ 6FA09D731D9E861600EDCA64 /* Sources */ = { isa = PBXGroup; children = ( + 6F5543EC20ADA2950049ECB2 /* FontsTestCase.m */, + 6FA09D741D9E861600EDCA64 /* ColorTestCase.m */, 6FE08CD91E8AA8E300EC4716 /* Helpers */, - 6FA09D741D9E861600EDCA64 /* FontsTestCase.m */, 6F5769B01EBCBE6100F931CA /* ImageTestCase.m */, ); path = Sources; @@ -449,7 +452,8 @@ buildActionMask = 2147483647; files = ( 6F5769B11EBCBE6100F931CA /* ImageTestCase.m in Sources */, - 6FA09D761D9E861600EDCA64 /* FontsTestCase.m in Sources */, + 6F5543ED20ADA2960049ECB2 /* FontsTestCase.m in Sources */, + 6FA09D761D9E861600EDCA64 /* ColorTestCase.m in Sources */, 6FE08CDB1E8AA8E300EC4716 /* BundleFix.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -531,7 +535,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -586,7 +590,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Tests/Sources/ColorTestCase.m b/Tests/Sources/ColorTestCase.m new file mode 100644 index 0000000..37b691d --- /dev/null +++ b/Tests/Sources/ColorTestCase.m @@ -0,0 +1,40 @@ +// +// Copyright (c) SRG SSR. All rights reserved. +// +// License information is available from the LICENSE file. +// + +#import +#import + +@interface ColorTestCase : XCTestCase + +@end + +@implementation ColorTestCase + +- (void)testColorFromHexadecimalString +{ + XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"#ff0000"], UIColor.redColor); + XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"#00ff00"], UIColor.greenColor); + XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"#0000ff"], UIColor.blueColor); + + XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"ff0000"], UIColor.redColor); + + XCTAssertNil([UIColor srg_colorFromHexadecimalString:@"#zzzzzz"]); +} + +- (void)testHexadecimalColorTransformer +{ + XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#ff0000"], UIColor.redColor); + XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#00ff00"], UIColor.greenColor); + XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#0000ff"], UIColor.blueColor); + + XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"ff0000"], UIColor.redColor); + + XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:UIColor.redColor], @"#ff0000"); + XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:UIColor.greenColor], @"#00ff00"); + XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:UIColor.blueColor], @"#0000ff"); +} + +@end