Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

537 lines (425 sloc) 16.721 kb
//
// DemoTextViewController.m
// CoreTextExtensions
//
// Created by Oliver Drobnik on 1/9/11.
// Copyright 2011 Drobnik.com. All rights reserved.
//
#import "DemoTextViewController.h"
#import <QuartzCore/QuartzCore.h>
#import <MediaPlayer/MediaPlayer.h>
@interface DemoTextViewController ()
- (void)_segmentedControlChanged:(id)sender;
- (void)linkPushed:(DTLinkButton *)button;
- (void)linkLongPressed:(UILongPressGestureRecognizer *)gesture;
- (void)debugButton:(UIBarButtonItem *)sender;
@property (nonatomic, strong) NSMutableSet *mediaPlayers;
@end
@implementation DemoTextViewController
#pragma mark NSObject
- (id)init {
if ((self = [super init])) {
NSArray *items = [[NSArray alloc] initWithObjects:@"View", @"Ranges", @"Chars", @"HTML", nil];
_segmentedControl = [[UISegmentedControl alloc] initWithItems:items];
_segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
_segmentedControl.selectedSegmentIndex = 0;
[_segmentedControl addTarget:self action:@selector(_segmentedControlChanged:) forControlEvents:UIControlEventValueChanged];
self.navigationItem.titleView = _segmentedControl;
// toolbar
#if 0 // DTWebArchive moved to separate project late 2011
UIBarButtonItem *spacer = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease];
UIBarButtonItem *paste = [[[UIBarButtonItem alloc] initWithTitle:@"Paste" style:UIBarButtonItemStyleBordered target:self action:@selector(paste:)] autorelease];
UIBarButtonItem *copy = [[[UIBarButtonItem alloc] initWithTitle:@"Copy" style:UIBarButtonItemStyleBordered target:self action:@selector(copy:)] autorelease];
#endif
UIBarButtonItem *debug = [[UIBarButtonItem alloc] initWithTitle:@"Debug Frames" style:UIBarButtonItemStyleBordered target:self action:@selector(debugButton:)];
NSArray *toolbarItems = [NSArray arrayWithObjects:/*paste, copy, spacer, */debug, nil];
[self setToolbarItems:toolbarItems];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
#pragma mark UIViewController
- (void)loadView {
[super loadView];
CGRect frame = CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height);
// Create chars view
_charsView = [[UITextView alloc] initWithFrame:frame];
_charsView.editable = NO;
_charsView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_charsView];
// Create range view
_rangeView = [[UITextView alloc] initWithFrame:frame];
_rangeView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_rangeView.editable = NO;
[self.view addSubview:_rangeView];
// Create html view
_htmlView = [[UITextView alloc] initWithFrame:frame];
_htmlView.editable = NO;
_htmlView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_htmlView];
// Create text view
[DTAttributedTextContentView setLayerClass:[CATiledLayer class]];
_textView = [[DTAttributedTextView alloc] initWithFrame:frame];
_textView.textDelegate = self;
_textView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.view addSubview:_textView];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Load HTML data
NSString *readmePath = [[NSBundle mainBundle] pathForResource:_fileName ofType:nil];
NSString *html = [NSString stringWithContentsOfFile:readmePath encoding:NSUTF8StringEncoding error:NULL];
NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding];
// Create attributed string from HTML
CGSize maxImageSize = CGSizeMake(self.view.bounds.size.width - 20.0, self.view.bounds.size.height - 20.0);
// example for setting a willFlushCallback, that gets called before elements are written to the generated attributed string
void (^callBackBlock)(DTHTMLElement *element) = ^(DTHTMLElement *element) {
// if an element is larger than twice the font size put it in it's own block
if (element.displayStyle == DTHTMLElementDisplayStyleInline && element.textAttachment.displaySize.height > 2.0 * element.fontDescriptor.pointSize)
{
element.displayStyle = DTHTMLElementDisplayStyleBlock;
}
};
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:1.0], NSTextSizeMultiplierDocumentOption, [NSValue valueWithCGSize:maxImageSize], DTMaxImageSize,
@"Times New Roman", DTDefaultFontFamily, @"purple", DTDefaultLinkColor, baseURL, NSBaseURLDocumentOption, callBackBlock, DTWillFlushBlockCallBack, nil];
NSAttributedString *string = [[NSAttributedString alloc] initWithHTMLData:data options:options documentAttributes:NULL];
// Display string
_textView.contentView.edgeInsets = UIEdgeInsetsMake(10, 10, 10, 10);
_textView.attributedString = string;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect bounds = self.view.bounds;
_textView.frame = bounds;
[self _segmentedControlChanged:nil];
[_textView setContentInset:UIEdgeInsetsMake(0, 0, 44, 0)];
[_textView setScrollIndicatorInsets:UIEdgeInsetsMake(0, 0, 44, 0)];
[self.navigationController setToolbarHidden:NO animated:YES];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// now the bar is up so we can autoresize again
_textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
- (void)viewWillDisappear:(BOOL)animated;
{
[self.navigationController setToolbarHidden:YES animated:YES];
// stop all playing media
for (MPMoviePlayerController *player in self.mediaPlayers)
{
[player stop];
}
[super viewWillDisappear:animated];
}
#pragma mark Private Methods
- (void)updateDetailViewForIndex:(NSUInteger)index
{
switch (index)
{
case 1:
{
NSMutableString *dumpOutput = [[NSMutableString alloc] init];
NSDictionary *attributes = nil;
NSRange effectiveRange = NSMakeRange(0, 0);
if ([_textView.attributedString length])
{
while ((attributes = [_textView.attributedString attributesAtIndex:effectiveRange.location effectiveRange:&effectiveRange]))
{
[dumpOutput appendFormat:@"Range: (%d, %d), %@\n\n", effectiveRange.location, effectiveRange.length, attributes];
effectiveRange.location += effectiveRange.length;
if (effectiveRange.location >= [_textView.attributedString length])
{
break;
}
}
}
_rangeView.text = dumpOutput;
break;
}
case 2:
{
// Create characters view
NSMutableString *dumpOutput = [[NSMutableString alloc] init];
NSData *dump = [[_textView.attributedString string] dataUsingEncoding:NSUTF8StringEncoding];
for (NSInteger i = 0; i < [dump length]; i++)
{
char *bytes = (char *)[dump bytes];
char b = bytes[i];
[dumpOutput appendFormat:@"%i: %x %c\n", i, b, b];
}
_charsView.text = dumpOutput;
break;
}
case 3:
{
_htmlView.text = [_textView.attributedString htmlString];
break;
}
}
}
- (void)_segmentedControlChanged:(id)sender {
UIScrollView *selectedView = _textView;
switch (_segmentedControl.selectedSegmentIndex) {
case 1:
selectedView = _rangeView;
break;
case 2:
selectedView = _charsView;
break;
case 3:
{
selectedView = _htmlView;
break;
}
}
// refresh only this tab
[self updateDetailViewForIndex:_segmentedControl.selectedSegmentIndex];
[self.view bringSubviewToFront:selectedView];
[selectedView flashScrollIndicators];
}
#pragma mark Custom Views on Text
- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForLink:(NSURL *)url identifier:(NSString *)identifier frame:(CGRect)frame
{
DTLinkButton *button = [[DTLinkButton alloc] initWithFrame:frame];
button.URL = url;
button.minimumHitSize = CGSizeMake(25, 25); // adjusts it's bounds so that button is always large enough
button.GUID = identifier;
// use normal push action for opening URL
[button addTarget:self action:@selector(linkPushed:) forControlEvents:UIControlEventTouchUpInside];
// demonstrate combination with long press
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(linkLongPressed:)];
[button addGestureRecognizer:longPress];
return button;
}
- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttachment:(DTTextAttachment *)attachment frame:(CGRect)frame
{
if (attachment.contentType == DTTextAttachmentTypeVideoURL)
{
NSURL *url = (id)attachment.contentURL;
// we could customize the view that shows before playback starts
UIView *grayView = [[UIView alloc] initWithFrame:frame];
grayView.backgroundColor = [DTColor blackColor];
// find a player for this URL if we already got one
MPMoviePlayerController *player = nil;
for (player in self.mediaPlayers)
{
if ([player.contentURL isEqual:url])
{
break;
}
}
if (!player)
{
player = [[MPMoviePlayerController alloc] initWithContentURL:url];
[self.mediaPlayers addObject:player];
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_4_2
NSString *airplayAttr = [attachment.attributes objectForKey:@"x-webkit-airplay"];
if ([airplayAttr isEqualToString:@"allow"])
{
if ([player respondsToSelector:@selector(setAllowsAirPlay:)])
{
player.allowsAirPlay = YES;
}
}
#endif
NSString *controlsAttr = [attachment.attributes objectForKey:@"controls"];
if (controlsAttr)
{
player.controlStyle = MPMovieControlStyleEmbedded;
}
else
{
player.controlStyle = MPMovieControlStyleNone;
}
NSString *loopAttr = [attachment.attributes objectForKey:@"loop"];
if (loopAttr)
{
player.repeatMode = MPMovieRepeatModeOne;
}
else
{
player.repeatMode = MPMovieRepeatModeNone;
}
NSString *autoplayAttr = [attachment.attributes objectForKey:@"autoplay"];
if (autoplayAttr)
{
player.shouldAutoplay = YES;
}
else
{
player.shouldAutoplay = NO;
}
[player prepareToPlay];
player.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
player.view.frame = grayView.bounds;
[grayView addSubview:player.view];
return grayView;
}
else if (attachment.contentType == DTTextAttachmentTypeImage)
{
// if the attachment has a hyperlinkURL then this is currently ignored
DTLazyImageView *imageView = [[DTLazyImageView alloc] initWithFrame:frame];
imageView.delegate = self;
if (attachment.contents)
{
imageView.image = attachment.contents;
}
// url for deferred loading
imageView.url = attachment.contentURL;
// if there is a hyperlink then add a link button on top of this image
if (attachment.hyperLinkURL)
{
// NOTE: this is a hack, you probably want to use your own image view and touch handling
// also, this treats an image with a hyperlink by itself because we don't have the GUID of the link parts
imageView.userInteractionEnabled = YES;
DTLinkButton *button = (DTLinkButton *)[self attributedTextContentView:attributedTextContentView viewForLink:attachment.hyperLinkURL identifier:attachment.hyperLinkGUID frame:imageView.bounds];
[imageView addSubview:button];
}
return imageView;
}
else if (attachment.contentType == DTTextAttachmentTypeIframe)
{
DTWebVideoView *videoView = [[DTWebVideoView alloc] initWithFrame:frame];
videoView.attachment = attachment;
return videoView;
}
else if (attachment.contentType == DTTextAttachmentTypeObject)
{
// somecolorparameter has a HTML color
UIColor *someColor = [UIColor colorWithHTMLName:[attachment.attributes objectForKey:@"somecolorparameter"]];
UIView *someView = [[UIView alloc] initWithFrame:frame];
someView.backgroundColor = someColor;
someView.layer.borderWidth = 1;
someView.layer.borderColor = [UIColor blackColor].CGColor;
return someView;
}
return nil;
}
- (BOOL)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView shouldDrawBackgroundForTextBlock:(DTTextBlock *)textBlock frame:(CGRect)frame context:(CGContextRef)context forLayoutFrame:(DTCoreTextLayoutFrame *)layoutFrame
{
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:10];
CGColorRef color = [textBlock.backgroundColor CGColor];
if (color)
{
CGContextSetFillColorWithColor(context, color);
CGContextAddPath(context, [roundedRect CGPath]);
CGContextFillPath(context);
CGContextAddPath(context, [roundedRect CGPath]);
CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
CGContextStrokePath(context);
return NO;
}
return YES; // draw standard background
}
#pragma mark Actions
- (void)linkPushed:(DTLinkButton *)button
{
NSURL *URL = button.URL;
if ([[UIApplication sharedApplication] canOpenURL:[URL absoluteURL]])
{
[[UIApplication sharedApplication] openURL:[URL absoluteURL]];
}
else
{
if (![URL host] && ![URL path])
{
// possibly a local anchor link
NSString *fragment = [URL fragment];
if (fragment)
{
[_textView scrollToAnchorNamed:fragment animated:NO];
}
}
}
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex != actionSheet.cancelButtonIndex)
{
[[UIApplication sharedApplication] openURL:[self.lastActionLink absoluteURL]];
}
}
- (void)linkLongPressed:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
DTLinkButton *button = (id)[gesture view];
button.highlighted = NO;
self.lastActionLink = button.URL;
if ([[UIApplication sharedApplication] canOpenURL:[button.URL absoluteURL]])
{
UIActionSheet *action = [[UIActionSheet alloc] initWithTitle:[[button.URL absoluteURL] description] delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Open in Safari", nil];
[action showFromRect:button.frame inView:button.superview animated:YES];
}
}
}
- (void)debugButton:(UIBarButtonItem *)sender
{
_textView.contentView.drawDebugFrames = !_textView.contentView.drawDebugFrames;
[DTCoreTextLayoutFrame setShouldDrawDebugFrames:_textView.contentView.drawDebugFrames];
[self.view setNeedsDisplay];
}
#if 0 // DTWebArchive split out late 2011
- (void)paste:(id)sender
{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
DTWebArchive *webArchive = [pasteboard webArchive];
if (webArchive)
{
CGSize maxImageSize = CGSizeMake(self.view.bounds.size.width - 20.0, self.view.bounds.size.height - 20.0);
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:1.0], NSTextSizeMultiplierDocumentOption, [NSValue valueWithCGSize:maxImageSize], DTMaxImageSize,
@"Times New Roman", DTDefaultFontFamily, @"purple", DTDefaultLinkColor, baseURL, NSBaseURLDocumentOption, nil];
NSAttributedString *attrString = [[[NSAttributedString alloc] initWithWebArchive:webArchive options:options documentAttributes:NULL] autorelease];
_textView.attributedString = attrString;
}
}
- (void)copy:(id)sender
{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
// web archive contains rich text
DTWebArchive *webArchive = [_textView.attributedString webArchive];
[pasteboard setWebArchive:webArchive];
// PS: in real life you also want to put put a plain text copy in pasteboard for apps that don't take rich text
}
#endif
#pragma mark DTLazyImageViewDelegate
- (void)lazyImageView:(DTLazyImageView *)lazyImageView didChangeImageSize:(CGSize)size {
NSURL *url = lazyImageView.url;
CGSize imageSize = size;
NSPredicate *pred = [NSPredicate predicateWithFormat:@"contentURL == %@", url];
// update all attachments that matchin this URL (possibly multiple images with same size)
for (DTTextAttachment *oneAttachment in [_textView.contentView.layoutFrame textAttachmentsWithPredicate:pred])
{
oneAttachment.originalSize = imageSize;
if (!CGSizeEqualToSize(imageSize, oneAttachment.displaySize))
{
oneAttachment.displaySize = imageSize;
}
}
// redo layout
// here we're layouting the entire string, might be more efficient to only relayout the paragraphs that contain these attachments
[_textView.contentView relayoutText];
}
#pragma mark Properties
- (NSMutableSet *)mediaPlayers
{
if (!mediaPlayers)
{
mediaPlayers = [[NSMutableSet alloc] init];
}
return mediaPlayers;
}
@synthesize fileName = _fileName;
@synthesize lastActionLink;
@synthesize mediaPlayers;
@synthesize baseURL;
@end
Jump to Line
Something went wrong with that request. Please try again.