blob: 145cb16cc356a13910730a1bc092921658f0b9ba [file] [log] [blame]
//
// 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