Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Text attachments and origin fixes #5

Merged
merged 2 commits into from

3 participants

@mikeash

I added some rudimentary support for text attachments (basically, custom drawing or UIViews embedded in the text). I also fixed up some coordinate transforms which were incorrect for indented text (and were affecting the placement of the attachments in my testing). Both could probably stand to be tested a little more....

Michael Ash added some commits
Michael Ash take into account line X origins when generating rects and otherwise …
…manipulating text coordinates, in order to correctly handle indented text
6b345f5
Michael Ash add support for text attachments d8622cc
@devindoty devindoty merged commit 2e9f108 into from
@voicecho

Q1: _attachmentViews in EGOTextView.m seems not been alloc and init. how could the following code work:

NSLog(@"_attachmentViews :%@",_attachmentViews);
for (UIView *view in _attachmentViews) {
    [view removeFromSuperview];
}
[_attributedString enumerateAttribute: EGOTextAttachmentAttributeName inRange: NSMakeRange(0, [_attributedString length]) options: 0 usingBlock: ^(id value, NSRange range, BOOL *stop) {

    if ([value respondsToSelector: @selector(attachmentView)]) {
        UIView *view = [value attachmentView];
        [_attachmentViews addObject: view];
        NSLog(@"_attachmentViews :%@",_attachmentViews);

        CGRect rect = [self firstRectForNSRange: range];
        rect.size = [view frame].size;
        [view setFrame: rect];
        [self addSubview: view];
    }
}];

Q2: Im using the following code to display an image in EGOTextView. But how could I delete it?

    AttachmentImageViewCell *attachmentCell = [[AttachmentImageViewCell alloc] initWithImage:[UIImage imageNamed:@"aa.png"]];
    attachmentCell.frame = CGRectMake(0, 0, 100, 100);
    NSDictionary *attachmentDict = [NSDictionary dictionaryWithObject:attachmentCell forKey:EGOTextAttachmentAttributeName];

    NSAttributedString *atrStr = [[NSAttributedString alloc] initWithString:@"picture" attributes:attachmentDict];
    self.egoTextView.attributedString = atrStr;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 29, 2011
  1. take into account line X origins when generating rects and otherwise …

    Michael Ash authored
    …manipulating text coordinates, in order to correctly handle indented text
  2. add support for text attachments

    Michael Ash authored
This page is out of date. Refresh to see the latest.
Showing with 122 additions and 7 deletions.
  1. +18 −0 EGOTextView/EGOTextView.h
  2. +104 −7 EGOTextView/EGOTextView.m
View
18 EGOTextView/EGOTextView.h
@@ -27,6 +27,10 @@
#import <UIKit/UITextChecker.h>
#include <objc/runtime.h>
+
+extern NSString * const EGOTextAttachmentAttributeName;
+extern NSString * const EGOTextAttachmentPlaceholderString;
+
@class EGOTextView;
@protocol EGOTextViewDelegate <NSObject, UIScrollViewDelegate>
@optional
@@ -45,6 +49,18 @@
@end
+@protocol EGOTextAttachmentCell <NSObject>
+@optional
+
+// the attachment must either implement -attachmentView or both
+// -attachmentSize and -attachmentDrawInRect:
+- (UIView *)attachmentView;
+
+- (CGSize) attachmentSize;
+- (void) attachmentDrawInRect: (CGRect)r;
+
+@end
+
@class EGOCaretView, EGOContentView, EGOTextWindow, EGOMagnifyView, EGOSelectionView;
@interface EGOTextView : UIScrollView <UITextInputTraits, UITextInput> {
@private
@@ -84,6 +100,8 @@
EGOCaretView *_caretView;
EGOSelectionView *_selectionView;
+ NSMutableArray *_attachmentViews;
+
}
@property(nonatomic) UIDataDetectorTypes dataDetectorTypes; // UIDataDetectorTypeLink supported
View
111 EGOTextView/EGOTextView.m
@@ -26,6 +26,9 @@
#import "EGOTextView.h"
#import <QuartzCore/QuartzCore.h>
+NSString * const EGOTextAttachmentAttributeName = @"com.enormego.EGOTextAttachmentAttribute";
+NSString * const EGOTextAttachmentPlaceholderString = @"\uFFFC";
+
typedef enum {
EGOWindowLoupe = 0,
EGOWindowMagnify,
@@ -36,6 +39,28 @@
EGOSelectionTypeRight,
} EGOSelectionType;
+// MARK: Text attachment helper functions
+static void AttachmentRunDelegateDealloc(void *refCon) {
+ [(id)refCon release];
+}
+
+static CGSize AttachmentRunDelegateGetSize(void *refCon) {
+ id <EGOTextAttachmentCell> cell = refCon;
+ if ([cell respondsToSelector: @selector(attachmentSize)]) {
+ return [cell attachmentSize];
+ } else {
+ return [[cell attachmentView] frame].size;
+ }
+}
+
+static CGFloat AttachmentRunDelegateGetDescent(void *refCon) {
+ return AttachmentRunDelegateGetSize(refCon).height;
+}
+
+static CGFloat AttachmentRunDelegateGetWidth(void *refCon) {
+ return AttachmentRunDelegateGetSize(refCon).width;
+}
+
// MARK: EGOContentView definition
@interface EGOContentView : UIView {
@@ -145,6 +170,7 @@ - (void)removeCorrectionAttributesForRange:(NSRange)range;
- (void)insertCorrectionAttributesForRange:(NSRange)range;
- (void)showCorrectionMenuForRange:(NSRange)range;
- (void)checkLinksForRange:(NSRange)range;
+- (void)scanAttachments;
- (void)showMenu;
- (CGRect)menuPresentationRect;
@@ -303,6 +329,22 @@ - (void)textChanged {
CFRelease(frameRef);
}
+ for (UIView *view in _attachmentViews) {
+ [view removeFromSuperview];
+ }
+ [_attributedString enumerateAttribute: EGOTextAttachmentAttributeName inRange: NSMakeRange(0, [_attributedString length]) options: 0 usingBlock: ^(id value, NSRange range, BOOL *stop) {
+
+ if ([value respondsToSelector: @selector(attachmentView)]) {
+ UIView *view = [value attachmentView];
+ [_attachmentViews addObject: view];
+
+ CGRect rect = [self firstRectForNSRange: range];
+ rect.size = [view frame].size;
+ [view setFrame: rect];
+ [self addSubview: view];
+ }
+ }];
+
[_textContentView setNeedsDisplay];
}
@@ -348,6 +390,7 @@ - (void)setAttributedString:(NSAttributedString*)string {
NSRange range = NSMakeRange(0, _attributedString.string.length);
if (!_editing && !_editable) {
[self checkLinksForRange:range];
+ [self scanAttachments];
}
[self textChanged];
@@ -511,7 +554,7 @@ - (void)drawBoundingRangeAsSelection:(NSRange)selectionRange cornerRadius:(CGFlo
CGFloat ascent, descent;
CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
- CGRect selectionRect = CGRectMake(xStart, origin.y - descent, xEnd - xStart, ascent + descent);
+ CGRect selectionRect = CGRectMake(origin.x + xStart, origin.y - descent, xEnd - xStart, ascent + descent);
if (range.length==1) {
selectionRect.size.width = _textContentView.bounds.size.width;
@@ -550,6 +593,25 @@ - (void)drawContentInRect:(CGRect)rect {
CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex((CFArrayRef)lines, i);
CGContextSetTextPosition(ctx, frameRect.origin.x + origins[i].x, frameRect.origin.y + origins[i].y);
CTLineDraw(line, ctx);
+
+ CFArrayRef runs = CTLineGetGlyphRuns(line);
+ CFIndex runsCount = CFArrayGetCount(runs);
+ for (CFIndex runsIndex = 0; runsIndex < runsCount; runsIndex++) {
+ CTRunRef run = CFArrayGetValueAtIndex(runs, runsIndex);
+ CFDictionaryRef attributes = CTRunGetAttributes(run);
+ id <EGOTextAttachmentCell> attachmentCell = [(id)attributes objectForKey: EGOTextAttachmentAttributeName];
+ if (attachmentCell != nil && [attachmentCell respondsToSelector: @selector(attachmentSize)] && [attachmentCell respondsToSelector: @selector(attachmentDrawInRect:)]) {
+ CGPoint position;
+ CTRunGetPositions(run, CFRangeMake(0, 1), &position);
+
+ CGSize size = [attachmentCell attachmentSize];
+ CGRect rect = { { origins[i].x + position.x, origins[i].y + position.y }, size };
+
+ UIGraphicsPushContext(UIGraphicsGetCurrentContext());
+ [attachmentCell attachmentDrawInRect: rect];
+ UIGraphicsPopContext();
+ }
+ }
}
free(origins);
@@ -572,7 +634,8 @@ - (NSInteger)closestWhiteSpaceIndexToPoint:(CGPoint)point {
CTLineRef line = (CTLineRef)[lines objectAtIndex:i];
CFRange cfRange = CTLineGetStringRange(line);
NSRange range = NSMakeRange(cfRange.location == kCFNotFound ? NSNotFound : cfRange.location, cfRange.length);
- CFIndex cfIndex = CTLineGetStringIndexForPosition(line, point);
+ CGPoint convertedPoint = CGPointMake(point.x - origins[i].x, point.y - origins[i].y);
+ CFIndex cfIndex = CTLineGetStringIndexForPosition(line, convertedPoint);
NSInteger index = cfIndex == kCFNotFound ? NSNotFound : cfIndex;
if(range.location==NSNotFound)
@@ -650,7 +713,8 @@ - (NSInteger)closestIndexToPoint:(CGPoint)point {
for (int i = 0; i < lines.count; i++) {
if (point.y > origins[i].y) {
CTLineRef line = (CTLineRef)[lines objectAtIndex:i];
- index = CTLineGetStringIndexForPosition(line, point);
+ CGPoint convertedPoint = CGPointMake(point.x - origins[i].x, point.y - origins[i].y);
+ index = CTLineGetStringIndexForPosition(line, convertedPoint);
break;
}
}
@@ -677,7 +741,8 @@ - (NSRange)characterRangeAtPoint_:(CGPoint)point {
if (point.y > origins[i].y) {
CTLineRef line = (CTLineRef)[lines objectAtIndex:i];
- NSInteger index = CTLineGetStringIndexForPosition(line, point);
+ CGPoint convertedPoint = CGPointMake(point.x - origins[i].x, point.y - origins[i].y);
+ NSInteger index = CTLineGetStringIndexForPosition(line, convertedPoint);
CFRange cfRange = CTLineGetStringRange(line);
NSRange range = NSMakeRange(cfRange.location == kCFNotFound ? NSNotFound : cfRange.location, cfRange.length);
@@ -760,7 +825,7 @@ - (CGRect)caretRectForIndex:(NSInteger)index {
free(origins);
origin.y -= self.font.leading;
- return CGRectMake(xPos, floorf(origin.y - descent), 3, ceilf((descent*2) + ascent));
+ return CGRectMake(origin.x + xPos, floorf(origin.y - descent), 3, ceilf((descent*2) + ascent));
}
@@ -795,7 +860,7 @@ - (CGRect)caretRectForIndex:(NSInteger)index {
}
- returnRect = CGRectMake(xPos, floorf(origin.y - descent), 3, ceilf((descent*2) + ascent));
+ returnRect = CGRectMake(origin.x + xPos, floorf(origin.y - descent), 3, ceilf((descent*2) + ascent));
}
@@ -830,7 +895,7 @@ - (CGRect)firstRectForNSRange:(NSRange)range {
CGFloat ascent, descent;
CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
- returnRect = [_textContentView convertRect:CGRectMake(xStart, origin.y - descent, xEnd - xStart, ascent + (descent*2)) toView:self];
+ returnRect = [_textContentView convertRect:CGRectMake(origin.x + xStart, origin.y - descent, xEnd - xStart, ascent + (descent*2)) toView:self];
break;
}
}
@@ -1475,6 +1540,38 @@ - (void)checkLinksForRange:(NSRange)range {
}
+- (void)scanAttachments {
+
+ __block NSMutableAttributedString *mutableAttributedString = nil;
+
+ [_attributedString enumerateAttribute: EGOTextAttachmentAttributeName inRange: NSMakeRange(0, [_attributedString length]) options: 0 usingBlock: ^(id value, NSRange range, BOOL *stop) {
+ // we only care when an attachment is set
+ if (value != nil) {
+ // create the mutable version of the string if it's not already there
+ if (mutableAttributedString == nil)
+ mutableAttributedString = [_attributedString mutableCopy];
+
+ CTRunDelegateCallbacks callbacks = {
+ .version = kCTRunDelegateVersion1,
+ .dealloc = AttachmentRunDelegateDealloc,
+ .getAscent = AttachmentRunDelegateGetDescent,
+ //.getDescent = AttachmentRunDelegateGetDescent,
+ .getWidth = AttachmentRunDelegateGetWidth
+ };
+
+ // the retain here is balanced by the release in the Dealloc function
+ CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, [value retain]);
+ [mutableAttributedString addAttribute: (NSString *)kCTRunDelegateAttributeName value: (id)runDelegate range:range];
+ CFRelease(runDelegate);
+ }
+ }];
+
+ if (mutableAttributedString) {
+ [_attributedString release];
+ _attributedString = mutableAttributedString;
+ }
+}
+
- (BOOL)selectedLinkAtIndex:(NSInteger)index {
NSTextCheckingResult *_link = [self linkAtIndex:index];
Something went wrong with that request. Please try again.