Skip to content

Instantly share code, notes, and snippets.

@wess
Created March 29, 2012 21:18
Show Gist options
  • Save wess/2243899 to your computer and use it in GitHub Desktop.
Save wess/2243899 to your computer and use it in GitHub Desktop.
Content View using core text with 1 embedded image
//
// WCAttributedString.h
// iOS
//
// Created by Wess Cope on 3/19/12.
//
// Special Thanks to Erica Sadun's Cookbook For the following, updated just a bit.
//
#import <Foundation/Foundation.h>
@interface NSAttributedString (Height)
- (CGSize)sizeForAttributedStringWithWidth:(CGFloat)width;
@end
@interface WCAttributedString : NSObject
@property (nonatomic, strong) NSMutableAttributedString *string;
@property (nonatomic, strong) UIFont *font;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, strong) NSString *textAlignment;
@property (nonatomic, strong) NSString *linebreakMode;
+ (id)attributedString;
- (void) append:(NSString *)formattedString, ...;
@end
//
// WCAttributedString.m
// iOS
//
// Created by Wess Cope on 3/19/12.
//
// Special Thanks to Erica Sadun's Cookbook For the following, updated just a bit.
//
#import "WCAttributedString.h"
#import <CoreText/CoreText.h>
@implementation NSAttributedString (Height)
- (CGSize)sizeForAttributedStringWithWidth:(CGFloat)width
{
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self);
CFIndex offset = 0, length;
CGFloat y = 0;
do {
length = CTTypesetterSuggestLineBreak(typesetter, offset, width);
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(offset, length));
CGFloat ascent, descent, leading;
CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
CFRelease(line);
offset += length;
y += ascent + descent + leading;
} while (offset < [self length]);
CFRelease(typesetter);
return CGSizeMake(width, ceil(y));
}
@end
#define MATCHSTART(STR1, STR2) ([[STR1 uppercaseString] hasPrefix:[STR2 uppercaseString]])
@implementation WCAttributedString
@synthesize string = _string;
@synthesize font = _font;
@synthesize color = _color;
@synthesize textAlignment = _textAlignment;
@synthesize linebreakMode = _linebreakMode;
- (id)init
{
self = [super init];
if (self)
{
_string = [[NSMutableAttributedString alloc] init];
_font = [UIFont systemFontOfSize:12.0f];
}
return self;
}
+ (id)attributedString
{
return [[self alloc] init];
}
- (uint8_t) ctAlignment
{
if (!_textAlignment) return kCTNaturalTextAlignment;
if (MATCHSTART(_textAlignment, @"n")) return kCTNaturalTextAlignment;
if (MATCHSTART(_textAlignment, @"l")) return kCTLeftTextAlignment;
if (MATCHSTART(_textAlignment, @"c")) return kCTCenterTextAlignment;
if (MATCHSTART(_textAlignment, @"r")) return kCTRightTextAlignment;
if (MATCHSTART(_textAlignment, @"j")) return kCTJustifiedTextAlignment;
return kCTNaturalTextAlignment;
}
- (uint8_t) ctBreakMode
{
if (!_linebreakMode) return kCTLineBreakByWordWrapping;
if (MATCHSTART(_linebreakMode, @"word")) return kCTLineBreakByWordWrapping;
if (MATCHSTART(_linebreakMode, @"char")) return kCTLineBreakByCharWrapping;
if (MATCHSTART(_linebreakMode, @"clip")) return kCTLineBreakByClipping;
if (MATCHSTART(_linebreakMode, @"head")) return kCTLineBreakByTruncatingHead;
if (MATCHSTART(_linebreakMode, @"tail")) return kCTLineBreakByTruncatingTail;
if (MATCHSTART(_linebreakMode, @"mid")) return kCTLineBreakByTruncatingMiddle;
return kCTLineBreakByWordWrapping;
}
- (CTParagraphStyleRef) newParagraphStyle
{
int addedTraits = 0;
if (_textAlignment) addedTraits++;
if (_linebreakMode) addedTraits++;
if (!addedTraits) return nil;
uint8_t theAlignment = [self ctAlignment];
CTParagraphStyleSetting alignSetting = {
kCTParagraphStyleSpecifierAlignment,
sizeof(uint8_t),
&theAlignment};
uint8_t theLineBreak = [self ctBreakMode];
CTParagraphStyleSetting wordBreakSetting = {
kCTParagraphStyleSpecifierLineBreakMode,
sizeof(uint8_t),
&theLineBreak};
CTParagraphStyleSetting settings[2] = {alignSetting, wordBreakSetting};
CTParagraphStyleRef paraStyle = CTParagraphStyleCreate(settings, 2);
return paraStyle;
}
- (void) append:(NSString *) formatstring, ...
{
if (!formatstring) return;
va_list arglist;
va_start(arglist, formatstring);
NSString *outstring = [[NSString alloc] initWithFormat:formatstring arguments:arglist];
va_end(arglist);
CTFontRef basicFontRef = CTFontCreateWithName((__bridge CFStringRef)_font.fontName, _font.pointSize, NULL);
NSMutableDictionary *basicFontAttr = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id) basicFontRef, (__bridge NSString *) kCTFontAttributeName,
nil];
CFRelease(basicFontRef);
if (_color)
[basicFontAttr setObject:(__bridge id) _color.CGColor forKey:(__bridge NSString *)kCTForegroundColorAttributeName];
CTParagraphStyleRef style = [self newParagraphStyle];
if (style)
{
[basicFontAttr setObject:(__bridge id)style forKey:(__bridge NSString *)kCTParagraphStyleAttributeName];
CFRelease(style);
}
NSAttributedString *newString = [[NSAttributedString alloc] initWithString:outstring attributes:basicFontAttr];
[self.string appendAttributedString:newString];
}
@end
//
// WCContentView.h
// iOS
//
// Created by Wess Cope on 3/26/12.
//
#import <UIKit/UIKit.h>
@interface WCContentView : UIView
{
NSAttributedString *_text;
UIImage *_image;
}
@property (nonatomic, readonly) CGFloat contentHeight;
- (id)initWithFrame:(CGRect)frame andText:(NSString *)text;
- (id)initWithFrame:(CGRect)frame text:(NSString *)text andImageURL:(NSString *)imageURL;
@end
//
// WCContentView.m
// iOS
//
// Created by Wess Cope on 3/26/12.
//
#import "BJContentView.h"
#import <CoreText/CoreText.h>
#import "SDImageCache.h"
#import "WCAttributedString.h"
@implementation BJContentView
@synthesize contentHeight = _contentHeight;
- (id)initWithFrame:(CGRect)frame andText:(NSString *)text
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = CLEAR;
self.opaque = NO;
WCAttributedString *aString = [WCAttributedString attributedString];
aString.font = [UIFont systemFontOfSize:18.0f];
[aString append:text];
_text = aString.string;
CFRange range;
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFMutableAttributedStringRef)_text);
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), nil, CGSizeMake(frame.size.width, 10000), &range);
_contentHeight = textSize.height + 150.0f;
frame.size.height = _contentHeight;
self.frame = frame;
_image = nil;
CFRelease(framesetter);
}
return self;
}
- (id)initWithFrame:(CGRect)frame text:(NSString *)text andImageURL:(NSString *)imageURL
{
self = [self initWithFrame:frame andText:text];
if(self)
{
UIImage *cachedImage = [[SDImageCache sharedImageCache] imageFromKey:imageURL];
if(!cachedImage)
{
cachedImage = IMAGE_FROM_URL(imageURL);
BOOL isLandscapeImage = ((cachedImage.size.height + 16.0f) >= cachedImage.size.width)? NO : YES;
cachedImage = [cachedImage scaleImageToWidth:((isLandscapeImage)? frame.size.width : (frame.size.width / 3) )];
[[SDImageCache sharedImageCache] storeImage:cachedImage forKey:imageURL];
}
_image = cachedImage;
_contentHeight += _image.size.height + 10.0f;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGRect imageFrame = CGRectZero;
if(_image)
{
BOOL isLandscapeImage = ((_image.size.height + 16.0f) >= _image.size.width)? NO : YES;
imageFrame.size.width = (isLandscapeImage)? rect.size.width : _image.size.width;
imageFrame.size.height = _image.size.height;
imageFrame.origin.y = rect.origin.y;
imageFrame.origin.x = (isLandscapeImage)? rect.origin.x : (rect.size.width - imageFrame.size.width);
//[_image drawInRect:CGRectInset(imageFrame, IMAGE_INSET, IMAGE_INSET)];
UIImageView *imageView = [[UIImageView alloc] initWithImage:_image];
imageView.frame = CGRectInset(imageFrame, ARTICLE_VIEW_IMAGE_INSET, ARTICLE_VIEW_IMAGE_INSET);
imageView.autoresizingMask = FLEX_SIZE;
imageView.borderColor = WHITE;
imageView.borderWidth = ARTICLE_VIEW_IMAGE_INSET;
LAYER_DROP_SHADOW(imageView, BLACK, CGSizeMake(0.0f, 4.0f), 0.2f, 2.0f);
[self addSubview:imageView];
}
CFMutableAttributedStringRef attrStringRef = (__bridge CFMutableAttributedStringRef)_text;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, rect);
CGMutablePathRef imageClippingPath = CGPathCreateMutable();
CGPathAddRect(imageClippingPath, NULL, CGRectMake((CGRectGetMaxX(rect) - CGRectGetWidth(imageFrame)),
CGRectGetMaxY(rect) - (CGRectGetHeight(imageFrame)),
CGRectGetWidth(imageFrame), CGRectGetHeight(imageFrame)));
CFStringRef keys[] = { kCTFramePathClippingPathAttributeName };
CFTypeRef values[] = { imageClippingPath };
CFDictionaryRef clippingPathDict = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, (sizeof(keys) / sizeof(keys[0])), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
// An array of clipping paths -- you can use more than one if needed!
NSArray *clippingPaths = [NSArray arrayWithObject:(__bridge NSDictionary*)clippingPathDict];
// Create an options dictionary, to pass in to CTFramesetter
NSDictionary *optionsDict = [NSDictionary dictionaryWithObject:clippingPaths forKey:(NSString*)kCTFrameClippingPathsAttributeName];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrStringRef);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, (__bridge CFDictionaryRef)optionsDict);
CTFrameDraw(frame, context);
CFRelease(frame);
CFRelease(framesetter);
CFRelease(path);
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment