Skip to content

Commit

Permalink
Merge branch 'release/2.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
defagos committed Mar 31, 2020
2 parents 22bbefb + 0409b76 commit c95cc77
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Common.xcconfig
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Version information
MARKETING_VERSION = 2.0.0
MARKETING_VERSION = 2.1.0

// Deployment targets
IPHONEOS_DEPLOYMENT_TARGET = 9.0
Expand Down
13 changes: 11 additions & 2 deletions Framework/Sources/UIColor+SRGAppearance.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,20 @@ OBJC_EXPORT NSValueTransformer *SRGHexadecimalColorTransformer(void);
@property (class, nonatomic, readonly) 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.
* Return the color matching a hexadecimal #rrggbbaa or #rrggbb string representation (the leading wildcard is optional),
* or `nil` if the string does not correspond to a color.
*
* @discussion Supports uppercase or lowercase digits.
*/
+ (nullable UIColor *)srg_colorFromHexadecimalString:(NSString *)hexadecimalString;

/**
* Return the color as a hexadecimal #rrggbbaa (#rrggbb if the alpha channel is 1) string representation.
*
* @discussion Always return lowercase digits with a leading wildcard.
*/
@property (nonatomic, readonly, copy) NSString *srg_hexadecimalString;

@end

NS_ASSUME_NONNULL_END
46 changes: 38 additions & 8 deletions Framework/Sources/UIColor+SRGAppearance.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ @interface SRGHexadecimalColorValueTransformer : NSValueTransformer

@implementation UIColor (SRGAppearance)

#pragma mark Class methods

+ (UIColor *)srg_redColor
{
return [UIColor srg_colorFromHexadecimalString:@"#9d0018"];
Expand All @@ -27,6 +29,13 @@ + (UIColor *)srg_colorFromHexadecimalString:(NSString *)hexadecimalString
return [SRGHexadecimalColorTransformer() transformedValue:hexadecimalString];
}

#pragma mark Getters and setters

- (NSString *)srg_hexadecimalString
{
return [SRGHexadecimalColorTransformer() reverseTransformedValue:self];
}

@end

@implementation SRGHexadecimalColorValueTransformer
Expand All @@ -49,20 +58,30 @@ - (id)transformedValue:(id)value
return nil;
}

NSScanner *scanner = [NSScanner scannerWithString:value];
if ([value hasPrefix:@"#"]) {
scanner.scanLocation = 1;
NSString *string = [value hasPrefix:@"#"] ? [value substringFromIndex:1] : value;
if (string.length != 6 && string.length != 8) {
return nil;
}

unsigned rgbValue = 0;
NSScanner *scanner = [NSScanner scannerWithString:string];
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];
if (string.length == 8) {
CGFloat red = ((rgbValue & 0xFF000000) >> 24) / 255.f;
CGFloat green = ((rgbValue & 0x00FF0000) >> 16) / 255.f;
CGFloat blue = ((rgbValue & 0x0000FF00) >> 8) / 255.f;
CGFloat alpha = (rgbValue & 0x000000FF) / 255.f;
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
else {
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
Expand All @@ -72,7 +91,18 @@ - (id)reverseTransformedValue:(id)value
}

const CGFloat *components = CGColorGetComponents([value CGColor]);
return [NSString stringWithFormat:@"#%02lx%02lx%02lx", lroundf(components[0] * 255), lroundf(components[1] * 255), lroundf(components[2] * 255)];

long red = lroundf(components[0] * 255);
long green = lroundf(components[1] * 255);
long blue = lroundf(components[2] * 255);
long alpha = lroundf(components[3] * 255);

if (alpha == 255) {
return [NSString stringWithFormat:@"#%02lx%02lx%02lx", red, green, blue];
}
else {
return [NSString stringWithFormat:@"#%02lx%02lx%02lx%02lx", red, green, blue, alpha];
}
}

@end
Expand Down
67 changes: 53 additions & 14 deletions Framework/Sources/UIImage+SRGAppearance.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,66 @@ NS_ASSUME_NONNULL_BEGIN
@interface UIImage (SRGAppearance)

/**
* Return an image generated from the vector image at the specified path.
*
* @param filePath The path of the vector image to use.
* @param size The size of the image to create.
*
* @return The generated image, `nil` if generation failed.
* Return an image generated from the vector image at the specified path. The image is fitted to the specified size
* and remaining space is filled with the provided color.
*
* @param filePath The path of the vector image to use.
* @param size The size of the image to create. Components set to 0 are calculated automatically based on the
* resource aspect ratio (or its intrinsic size if both are 0).
* @param fillColor The color to use for filling, transparent if `nil`.
*
* @return The generated image, `nil` if generation failed.
*/
+ (nullable UIImage *)srg_vectorImageAtPath:(NSString *)filePath withSize:(CGSize)size fillColor:(nullable UIColor *)fillColor;

/**
* Same as `-srg_vectorImageAtPath:withSize:fillColor:` without fill color.
*/
+ (nullable UIImage *)srg_vectorImageAtPath:(NSString *)filePath withSize:(CGSize)size;

/**
* Return the file URL of an image generated from the vector image at the specified path.
*
* @param filePath The path of the vector image to use.
* @param size The size of the image to create.
*
* @return The generated image, `nil` if generation failed.
*
* @discussion Images are stored in the `/Library/Caches` directory.
* Same as `-srg_vectorImageAtPath:withSize:fillColor:`, calculating the other dimension based on the image aspect ratio.
*/
+ (nullable UIImage *)srg_vectorImageAtPath:(NSString *)filePath withWidth:(CGFloat)width;
+ (nullable UIImage *)srg_vectorImageAtPath:(NSString *)filePath withHeight:(CGFloat)height;

/**
* Same as `-srg_vectorImageAtPath:withSize:fillColor:`, returning the image with its intrinsic size.
*/
+ (nullable UIImage *)srg_vectorImageAtPath:(NSString *)filePath;

/**
* Return the file URL of an image generated from the vector image at the specified path. The image is fitted to the
* specified size and remaining space is filled with the provided color.
*
* @param filePath The path of the vector image to use.
* @param size The size of the image to create. Components set to 0 are calculated automatically based on the
* resource aspect ratio (or its intrinsic size if both are 0).
* @param fillColor The color to use for filling, transparent if `nil`.
*
* @return The generated image, `nil` if generation failed.
*
* @discussion Images are stored in the `/Library/Caches` directory.
*/
+ (nullable NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withSize:(CGSize)size fillColor:(nullable UIColor *)fillColor;

/**
* Same as `-srg_URLForVectorImageAtPath:withSize:fillColor:` without fill color.
*/
+ (nullable NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withSize:(CGSize)size;

/**
* Same as `-srg_URLForVectorImageAtPath:withSize:fillColor:`, calculating the other dimension based on the image
* aspect ratio.
*/
+ (nullable NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withWidth:(CGFloat)width;
+ (nullable NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withHeight:(CGFloat)height;

/**
* Same as `-srg_URLForVectorImageAtPath:withSize:fillColor:`, returning the image with its intrinsic size.
*/
+ (nullable NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath;

/**
* Clears the vector image cache.
*/
Expand Down
78 changes: 69 additions & 9 deletions Framework/Sources/UIImage+SRGAppearance.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

#import "UIImage+SRGAppearance.h"

#import "UIColor+SRGAppearance.h"

// Implementation borrowed from https://github.com/erica/useful-things

static CGFloat SRGAppearanceImageAspectScaleFit(CGSize sourceSize, CGRect destRect)
{
CGSize destSize = destRect.size;
Expand All @@ -27,7 +31,7 @@ static CGRect SRGAppearanceImageRectByFittingRect(CGRect sourceRect, CGRect dest
return SRGAppearanceImageRectAroundCenter(center, targetSize);
}

static void SRGAppearanceImageDrawPDFPageInRect(CGPDFPageRef pageRef, CGRect rect)
static void SRGAppearanceImageDrawPDFPageInRect(CGPDFPageRef pageRef, CGRect rect, CGColorRef fillColor)
{
CGContextRef context = UIGraphicsGetCurrentContext();

Expand All @@ -43,7 +47,12 @@ static void SRGAppearanceImageDrawPDFPageInRect(CGPDFPageRef pageRef, CGRect rec
// Flip the rect, which remains in UIKit space
CGRect d = CGRectApplyAffineTransform(rect, transform);

// Calculate a rectangle to draw to
if (fillColor) {
CGContextSetFillColorWithColor(context, fillColor);
CGContextFillRect(context, d);
}

// Calculate the rectangle to draw to
CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);
CGFloat drawingAspect = SRGAppearanceImageAspectScaleFit(pageRect.size, d);
CGRect drawingRect = SRGAppearanceImageRectByFittingRect(pageRect, d);
Expand All @@ -70,17 +79,36 @@ static void SRGAppearanceImageDrawPDFPageInRect(CGPDFPageRef pageRef, CGRect rec

@implementation UIImage (SRGAppearance)

// Implementation borrowed from https://github.com/erica/useful-things
+ (UIImage *)srg_vectorImageAtPath:(NSString *)filePath withSize:(CGSize)size
+ (UIImage *)srg_vectorImageAtPath:(NSString *)filePath withSize:(CGSize)size fillColor:(UIColor *)fillColor
{
NSURL *fileURL = [self srg_URLForVectorImageAtPath:filePath withSize:size];
NSURL *fileURL = [self srg_URLForVectorImageAtPath:filePath withSize:size fillColor:fillColor];
return fileURL ? [UIImage imageWithContentsOfFile:fileURL.path] : nil;
}

+ (NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withSize:(CGSize)size
+ (UIImage *)srg_vectorImageAtPath:(NSString *)filePath withSize:(CGSize)size
{
return [self srg_vectorImageAtPath:filePath withSize:size fillColor:nil];
}

+ (UIImage *)srg_vectorImageAtPath:(NSString *)filePath withWidth:(CGFloat)width
{
return [self srg_vectorImageAtPath:filePath withSize:CGSizeMake(width, 0.f) fillColor:nil];
}

+ (UIImage *)srg_vectorImageAtPath:(NSString *)filePath withHeight:(CGFloat)height
{
return [self srg_vectorImageAtPath:filePath withSize:CGSizeMake(0.f, height) fillColor:nil];
}

+ (UIImage *)srg_vectorImageAtPath:(NSString *)filePath
{
return [self srg_vectorImageAtPath:filePath withSize:CGSizeZero fillColor:nil];
}

+ (NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withSize:(CGSize)size fillColor:(UIColor *)fillColor
{
// Check cached image existence at the very beginning, and return it if available
NSString *cachedFileName = [NSString stringWithFormat:@"%@_%@_%@.png", @(filePath.hash), @(size.width), @(size.height)];
NSString *cachedFileName = [NSString stringWithFormat:@"%@_%@_%@_%@.png", @(filePath.hash), @(size.width), @(size.height), fillColor.srg_hexadecimalString ?: @"none"];
NSString *cachesDirectory = SRGAppearanceVectorImageCachesDirectory();
NSString *cachedFilePath = [cachesDirectory stringByAppendingPathComponent:cachedFileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:cachedFilePath]) {
Expand All @@ -100,9 +128,21 @@ + (NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withSize:(CGSize)siz
return nil;
}

UIGraphicsBeginImageContextWithOptions(size, NO, 1. /* Force scale to 1 (0 would use device scale) */);
CGPDFPageRef pageRef = CGPDFDocumentGetPage(pdfDocumentRef, 1);
SRGAppearanceImageDrawPDFPageInRect(pageRef, CGRectMake(0.f, 0.f, size.width, size.height));
CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);

if (CGSizeEqualToSize(size, CGSizeZero)) {
size = pageRect.size;
}
else if (size.width == 0.f) {
size.width = size.height * pageRect.size.width / pageRect.size.height;
}
else if (size.height == 0.f) {
size.height = size.width * pageRect.size.height / pageRect.size.width;
}

UIGraphicsBeginImageContextWithOptions(size, NO, 1. /* Force scale to 1 (0 would use device scale) */);
SRGAppearanceImageDrawPDFPageInRect(pageRef, CGRectMake(0.f, 0.f, size.width, size.height), fillColor.CGColor);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Expand All @@ -120,6 +160,26 @@ + (NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withSize:(CGSize)siz
return [NSURL fileURLWithPath:cachedFilePath];
}

+ (NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withSize:(CGSize)size
{
return [self srg_URLForVectorImageAtPath:filePath withSize:size fillColor:nil];
}

+ (NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withWidth:(CGFloat)width
{
return [self srg_URLForVectorImageAtPath:filePath withSize:CGSizeMake(width, 0.f) fillColor:nil];
}

+ (NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath withHeight:(CGFloat)height
{
return [self srg_URLForVectorImageAtPath:filePath withSize:CGSizeMake(0.f, height) fillColor:nil];
}

+ (NSURL *)srg_URLForVectorImageAtPath:(NSString *)filePath
{
return [self srg_URLForVectorImageAtPath:filePath withSize:CGSizeZero fillColor:nil];
}

+ (void)srg_clearVectorImageCache
{
NSString *cachesDirectory = SRGAppearanceVectorImageCachesDirectory();
Expand Down
37 changes: 37 additions & 0 deletions Tests/Sources/ColorTestCase.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,59 @@ - (void)testColorFromHexadecimalString
XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"#00ff00"], UIColor.greenColor);
XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"#0000ff"], UIColor.blueColor);

XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"#ff000080"], [UIColor.redColor colorWithAlphaComponent:128.f / 255.f]);
XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"#ff000000"], [UIColor.redColor colorWithAlphaComponent:0.f]);
XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"#ff0000ff"], [UIColor.redColor colorWithAlphaComponent:1.f]);

XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"FF0000"], UIColor.redColor);
XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"ff0000"], UIColor.redColor);

XCTAssertNil([UIColor srg_colorFromHexadecimalString:@"#zzzzzz"]);
}

- (void)testHexadecimalString
{
XCTAssertEqualObjects(UIColor.redColor.srg_hexadecimalString, @"#ff0000");
XCTAssertEqualObjects(UIColor.greenColor.srg_hexadecimalString, @"#00ff00");
XCTAssertEqualObjects(UIColor.blueColor.srg_hexadecimalString, @"#0000ff");

XCTAssertEqualObjects([UIColor.redColor colorWithAlphaComponent:128.f / 255.f].srg_hexadecimalString, @"#ff000080");
XCTAssertEqualObjects([UIColor.redColor colorWithAlphaComponent:0.5f].srg_hexadecimalString, @"#ff000080");

XCTAssertEqualObjects([UIColor srg_colorFromHexadecimalString:@"FF0000FF"].srg_hexadecimalString, @"#ff0000");
}

- (void)testHexadecimalColorTransformer
{
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#ff0000"], UIColor.redColor);
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#00ff00"], UIColor.greenColor);
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#0000ff"], UIColor.blueColor);

XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#ff0000ff"], UIColor.redColor);
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#00ff00ff"], UIColor.greenColor);
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#0000ffff"], UIColor.blueColor);

XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#ff000080"], [UIColor.redColor colorWithAlphaComponent:128.f / 255.f]);
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#00ff0080"], [UIColor.greenColor colorWithAlphaComponent:128.f / 255.f]);
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#0000ff80"], [UIColor.blueColor colorWithAlphaComponent:128.f / 255.f]);

XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#ff000000"], [UIColor.redColor colorWithAlphaComponent:0.f]);
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#00ff0000"], [UIColor.greenColor colorWithAlphaComponent:0.f]);
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"#0000ff00"], [UIColor.blueColor colorWithAlphaComponent:0.f]);

XCTAssertEqualObjects([SRGHexadecimalColorTransformer() transformedValue:@"ff0000"], UIColor.redColor);

XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:UIColor.redColor], @"#ff0000");
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:UIColor.greenColor], @"#00ff00");
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:UIColor.blueColor], @"#0000ff");

XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:[UIColor.redColor colorWithAlphaComponent:128.f / 255.f]], @"#ff000080");
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:[UIColor.greenColor colorWithAlphaComponent:128.f / 255.f]], @"#00ff0080");
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:[UIColor.blueColor colorWithAlphaComponent:128.f / 255.f]], @"#0000ff80");

XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:[UIColor.redColor colorWithAlphaComponent:0.f]], @"#ff000000");
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:[UIColor.greenColor colorWithAlphaComponent:0.f]], @"#00ff0000");
XCTAssertEqualObjects([SRGHexadecimalColorTransformer() reverseTransformedValue:[UIColor.blueColor colorWithAlphaComponent:0.f]], @"#0000ff00");
}

@end

0 comments on commit c95cc77

Please sign in to comment.