| // |
| // GTMFadeTruncatingLabel.m |
| // |
| // Copyright 2012 Google Inc. |
| // |
| // 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 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| // |
| #import "GTMFadeTruncatingLabel.h" |
| |
| @interface GTMFadeTruncatingLabel () |
| - (void)setup; |
| @end |
| |
| @implementation GTMFadeTruncatingLabel |
| |
| @synthesize truncateMode = truncateMode_; |
| |
| - (void)setup { |
| self.backgroundColor = [UIColor clearColor]; |
| truncateMode_ = GTMFadeTruncatingTail; |
| } |
| |
| - (id)initWithFrame:(CGRect)frame { |
| self = [super initWithFrame:frame]; |
| if (self) { |
| // Use clip as a default value. |
| self.lineBreakMode = NSLineBreakByClipping; |
| [self setup]; |
| } |
| return self; |
| } |
| |
| - (void)awakeFromNib { |
| [super awakeFromNib]; |
| [self setup]; |
| } |
| |
| // Draw fade gradient mask if text is wider than rect. |
| - (void)drawTextInRect:(CGRect)requestedRect { |
| CGContextRef context = UIGraphicsGetCurrentContext(); |
| CGContextSaveGState(context); |
| |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 |
| // |sizeWithFont:| is deprecated in iOS 7, replaced by |sizeWithAttributes:| |
| CGSize size = [self.text sizeWithFont:self.font]; |
| #else |
| CGSize size = CGSizeZero; |
| if (self.font) { |
| size = [self.text sizeWithAttributes:@{NSFontAttributeName:self.font}]; |
| // sizeWithAttributes: may return fractional values, so ceil the width and |
| // height to preserve the behavior of sizeWithFont:. |
| size = CGSizeMake(ceil(size.width), ceil(size.height)); |
| } |
| #endif |
| if (size.width > requestedRect.size.width) { |
| UIImage* image = [[self class] |
| getLinearGradient:requestedRect |
| fadeHead:((self.truncateMode & GTMFadeTruncatingHead) > 0) |
| fadeTail:((self.truncateMode & GTMFadeTruncatingTail) > 0)]; |
| CGContextClipToMask(context, self.bounds, image.CGImage); |
| } |
| |
| if (self.shadowColor) { |
| CGRect shadowRect = CGRectOffset(requestedRect, self.shadowOffset.width, |
| self.shadowOffset.height); |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 |
| // |drawInRect:withFont:lineBreakMode:alignment:| is deprecated in iOS 7, |
| // replaced by |drawInRect:withAttributes:| |
| CGContextSetFillColorWithColor(context, self.shadowColor.CGColor); |
| [self.text drawInRect:shadowRect |
| withFont:self.font |
| lineBreakMode:self.lineBreakMode |
| alignment:self.textAlignment]; |
| #else |
| if (self.font) { |
| NSMutableParagraphStyle* textStyle = |
| [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; |
| textStyle.lineBreakMode = self.lineBreakMode; |
| textStyle.alignment = self.textAlignment; |
| NSDictionary* attributes = @{ |
| NSFontAttributeName:self.font, |
| NSParagraphStyleAttributeName:textStyle, |
| NSForegroundColorAttributeName:self.shadowColor |
| }; |
| [self.text drawInRect:shadowRect |
| withAttributes:attributes]; |
| } |
| #endif |
| } |
| |
| // We check for nilness of shadowColor above, but there's no need to do so |
| // for textColor here because UILabel's textColor property cannot be nil. |
| // The UILabel docs say the default textColor is black and experimentation |
| // shows that calling -textColor will return the cached [UIColor blackColor] |
| // when called on a freshly alloc/init-ed UILabel, or a UILabel whose |
| // textColor has been set to nil. |
| // |
| // @see https://developer.apple.com/Library/ios/documentation/UIKit/Reference/UILabel_Class/Reference/UILabel.html#//apple_ref/occ/instp/UILabel/textColor |
| // (NOTE(bgoodwin): interesting side-note. These docs also say setting |
| // textColor to nil will result in an exception. In my testing, that did not |
| // happen.) |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 |
| // |drawInRect:withFont:lineBreakMode:alignment:| is deprecated in iOS 7, |
| // replaced by |drawInRect:withAttributes:| |
| CGContextSetFillColorWithColor(context, self.textColor.CGColor); |
| [self.text drawInRect:requestedRect |
| withFont:self.font |
| lineBreakMode:self.lineBreakMode |
| alignment:self.textAlignment]; |
| #else |
| if (self.font) { |
| NSMutableParagraphStyle* textStyle = |
| [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; |
| textStyle.lineBreakMode = self.lineBreakMode; |
| textStyle.alignment = self.textAlignment; |
| NSDictionary* attributes = @{ |
| NSFontAttributeName:self.font, |
| NSParagraphStyleAttributeName:textStyle, |
| NSForegroundColorAttributeName:self.textColor |
| }; |
| [self.text drawInRect:requestedRect |
| withAttributes:attributes]; |
| } |
| #endif |
| CGContextRestoreGState(context); |
| } |
| |
| // Create gradient opacity mask based on direction. |
| + (UIImage*)getLinearGradient:(CGRect)rect |
| fadeHead:(BOOL)fadeHead |
| fadeTail:(BOOL)fadeTail { |
| // Create an opaque context. |
| CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); |
| CGContextRef context = CGBitmapContextCreate(NULL, |
| rect.size.width, |
| rect.size.height, |
| 8, |
| 4*rect.size.width, |
| colorSpace, |
| (CGBitmapInfo)kCGImageAlphaNone); |
| |
| // White background will mask opaque, black gradient will mask transparent. |
| CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); |
| CGContextFillRect(context, rect); |
| |
| // Create gradient from white to black. |
| CGFloat locs[2] = { 0.0f, 1.0f }; |
| CGFloat components[4] = { 1.0f, 1.0f, 0.0f, 1.0f }; |
| CGGradientRef gradient = |
| CGGradientCreateWithColorComponents(colorSpace, components, locs, 2); |
| CGColorSpaceRelease(colorSpace); |
| |
| // Draw head and/or tail gradient. |
| CGFloat fadeWidth = MIN(rect.size.height * 2, floor(rect.size.width / 4)); |
| CGFloat minX = CGRectGetMinX(rect); |
| CGFloat maxX = CGRectGetMaxX(rect); |
| if (fadeTail) { |
| CGFloat startX = maxX - fadeWidth; |
| CGPoint startPoint = CGPointMake(startX, CGRectGetMidY(rect)); |
| CGPoint endPoint = CGPointMake(maxX, CGRectGetMidY(rect)); |
| CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); |
| } |
| if (fadeHead) { |
| CGFloat startX = minX + fadeWidth; |
| CGPoint startPoint = CGPointMake(startX, CGRectGetMidY(rect)); |
| CGPoint endPoint = CGPointMake(minX, CGRectGetMidY(rect)); |
| CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); |
| } |
| CGGradientRelease(gradient); |
| |
| // Clean up, return image. |
| CGImageRef ref = CGBitmapContextCreateImage(context); |
| UIImage* image = [UIImage imageWithCGImage:ref]; |
| CGImageRelease(ref); |
| CGContextRelease(context); |
| return image; |
| } |
| |
| @end |