diff --git a/EGOTextView/EGOTextView.h b/EGOTextView/EGOTextView.h index 67974aa..927631a 100644 --- a/EGOTextView/EGOTextView.h +++ b/EGOTextView/EGOTextView.h @@ -70,7 +70,7 @@ extern NSString * const EGOTextAttachmentPlaceholderString; UITextInputStringTokenizer *_tokenizer; UITextChecker *_textChecker; UILongPressGestureRecognizer *_longPress; - + BOOL _ignoreSelectionMenu; BOOL _delegateRespondsToShouldBeginEditing; BOOL _delegateRespondsToShouldEndEditing; @@ -79,47 +79,49 @@ extern NSString * const EGOTextAttachmentPlaceholderString; BOOL _delegateRespondsToDidChange; BOOL _delegateRespondsToDidChangeSelection; BOOL _delegateRespondsToDidSelectURL; - + NSAttributedString *_attributedString; - UIFont *_font; + UIFont *_font; BOOL _editing; - BOOL _editable; + BOOL _editable; BOOL _spellCheck; BOOL _dataDetectors; - - NSRange _markedRange; + + NSRange _markedRange; NSRange _selectedRange; NSRange _correctionRange; NSRange _linkRange; CTFramesetterRef _framesetter; CTFrameRef _frame; - + EGOContentView *_textContentView; EGOTextWindow *_textWindow; EGOCaretView *_caretView; EGOSelectionView *_selectionView; - + NSMutableArray *_attachmentViews; - + } @property(nonatomic) UIDataDetectorTypes dataDetectorTypes; // UIDataDetectorTypeLink supported @property(nonatomic) UITextAutocapitalizationType autocapitalizationType; -@property(nonatomic) UITextAutocorrectionType autocorrectionType; -@property(nonatomic) UIKeyboardType keyboardType; -@property(nonatomic) UIKeyboardAppearance keyboardAppearance; -@property(nonatomic) UIReturnKeyType returnKeyType; -@property(nonatomic) BOOL enablesReturnKeyAutomatically; +@property(nonatomic) UITextAutocorrectionType autocorrectionType; +@property(nonatomic) UIKeyboardType keyboardType; +@property(nonatomic) UIKeyboardAppearance keyboardAppearance; +@property(nonatomic) UIReturnKeyType returnKeyType; +@property(nonatomic) BOOL enablesReturnKeyAutomatically; @property(nonatomic,assign) id delegate; @property(nonatomic,copy) NSAttributedString *attributedString; @property(nonatomic,copy) NSString *text; @property(nonatomic,retain) UIFont *font; // ignored when attributedString is not nil @property(nonatomic,getter=isEditable) BOOL editable; //default YES +@property(nonatomic) BOOL correctable; //default YES @property(nonatomic) NSRange selectedRange; @property(nonatomic) NSRange markedRange; - (BOOL)hasText; +- (void)replaceNSRange:(NSRange)range withText:(NSString *)text; -@end \ No newline at end of file +@end diff --git a/EGOTextView/EGOTextView.m b/EGOTextView/EGOTextView.m index cb5b4ef..f1f8101 100644 --- a/EGOTextView/EGOTextView.m +++ b/EGOTextView/EGOTextView.m @@ -109,7 +109,7 @@ @interface EGOTextWindow : UIWindow { EGOWindowType _type; EGOSelectionType _selectionType; BOOL _showing; - + } @property(nonatomic,assign) EGOWindowType type; @property(nonatomic,assign) EGOSelectionType selectionType; @@ -160,7 +160,7 @@ + (EGOIndexedRange *)rangeWithNSRange:(NSRange)range; @interface EGOTextView (Private) -- (CGRect)caretRectForIndex:(int)index; +- (CGRect)caretRectForIndex:(NSInteger)index; - (CGRect)firstRectForNSRange:(NSRange)range; - (NSInteger)closestIndexToPoint:(CGPoint)point; - (NSRange)characterRangeAtPoint_:(CGPoint)point; @@ -172,6 +172,7 @@ - (void)showCorrectionMenuForRange:(NSRange)range; - (void)checkLinksForRange:(NSRange)range; - (void)scanAttachments; - (void)showMenu; + - (CGRect)menuPresentationRect; + (UIColor *)selectionColor; @@ -185,6 +186,7 @@ @interface EGOTextView () @property(nonatomic,retain) NSDictionary *correctionAttributes; @property(nonatomic,retain) NSMutableDictionary *menuItemActions; @property(nonatomic) NSRange correctionRange; +@property BOOL dos; @end @@ -195,6 +197,7 @@ @implementation EGOTextView @synthesize text=_text; @synthesize font=_font; @synthesize editable=_editable; +@synthesize correctable = _correctable; @synthesize markedRange=_markedRange; @synthesize selectedRange=_selectedRange; @synthesize correctionRange=_correctionRange; @@ -221,25 +224,25 @@ - (void)commonInit { self.backgroundColor = [UIColor whiteColor]; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.clipsToBounds = YES; - + EGOContentView *contentView = [[EGOContentView alloc] initWithFrame:CGRectInset(self.bounds, 8.0f, 8.0f)]; contentView.autoresizingMask = self.autoresizingMask; contentView.delegate = self; [self addSubview:contentView]; _textContentView = [contentView retain]; [contentView release]; - + UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; gesture.delegate = (id)self; [self addGestureRecognizer:gesture]; [gesture release]; _longPress = gesture; - + UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)]; [doubleTap setNumberOfTapsRequired:2]; [self addGestureRecognizer:doubleTap]; [doubleTap release]; - + UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; [self addGestureRecognizer:singleTap]; [singleTap release]; @@ -265,7 +268,7 @@ - (id)initWithCoder: (NSCoder *)aDecoder { } - (void)dealloc { - + _textWindow=nil; [_font release], _font=nil; [_attributedString release], _attributedString=nil; @@ -277,12 +280,12 @@ - (void)dealloc { } - (void)clearPreviousLayoutInformation { - + if (_framesetter != NULL) { CFRelease(_framesetter); _framesetter = NULL; } - + if (_frame != NULL) { CFRelease(_frame); _frame = NULL; @@ -290,35 +293,36 @@ - (void)clearPreviousLayoutInformation { } - (CGFloat)boundingWidthForHeight:(CGFloat)height { - + CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(_framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(CGFLOAT_MAX, height), NULL); - return suggestedSize.width; + return suggestedSize.width; } - (CGFloat)boundingHeightForWidth:(CGFloat)width { - + CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(_framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(width, CGFLOAT_MAX), NULL); return suggestedSize.height; } - (void)textChanged { - + if ([[UIMenuController sharedMenuController] isMenuVisible]) { [[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO]; } - + CTFramesetterRef framesetter = _framesetter; _framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attributedString); if (framesetter!=NULL) { - CFRelease(framesetter); + CFRelease(framesetter); } - + CGRect rect = _textContentView.frame; CGFloat height = [self boundingHeightForWidth:rect.size.width]; rect.size.height = height+self.font.lineHeight; - _textContentView.frame = rect; + rect.size.height = round(rect.size.height); + _textContentView.frame = rect; self.contentSize = CGSizeMake(self.frame.size.width, rect.size.height+(self.font.lineHeight*2)); UIBezierPath *path = [UIBezierPath bezierPathWithRect:_textContentView.bounds]; @@ -328,57 +332,61 @@ - (void)textChanged { if (frameRef!=NULL) { 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]; - + } - (NSString *)text { - return _attributedString.string; + NSString *text = _attributedString.string; + return self.dos ? [text stringByReplacingOccurrencesOfString:@"\n" withString:@"\r\n"] : text; } - (void)setFont:(UIFont *)font { - + UIFont *oldFont = _font; _font = [font retain]; [oldFont release]; - - CTFontRef ctFont = CTFontCreateWithName((CFStringRef) self.font.fontName, self.font.pointSize, NULL); + + CTFontRef ctFont = CTFontCreateWithName((CFStringRef) self.font.fontName, self.font.pointSize, NULL); NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:(id)ctFont, (NSString *)kCTFontAttributeName, (id)[UIColor blackColor].CGColor, kCTForegroundColorAttributeName, nil]; self.defaultAttributes = dictionary; [dictionary release]; - CFRelease(ctFont); - + CFRelease(ctFont); + [self textChanged]; - + } - (void)setText:(NSString *)text { - - [self.inputDelegate textWillChange:self]; - + if ([text rangeOfString:@"\r\n"].location == NSNotFound) { + self.dos = NO; + } else { + self.dos = YES; + text = [text stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"]; + } + [self.inputDelegate textWillChange:self]; NSAttributedString *string = [[NSAttributedString alloc] initWithString:text attributes:self.defaultAttributes]; [self setAttributedString:string]; + _mutableAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:string]; [string release]; - - [self.inputDelegate textDidChange:self]; - + [self.inputDelegate textDidChange:self]; } - (void)setAttributedString:(NSAttributedString*)string { @@ -386,26 +394,24 @@ - (void)setAttributedString:(NSAttributedString*)string { NSAttributedString *aString = _attributedString; _attributedString = [string copy]; [aString release], aString = nil; - + NSRange range = NSMakeRange(0, _attributedString.string.length); if (!_editing && !_editable) { [self checkLinksForRange:range]; [self scanAttachments]; } - - [self textChanged]; + [self textChanged]; if (_delegateRespondsToDidChange) { [self.delegate egoTextViewDidChange:self]; } - } - (void)setDelegate:(id)aDelegate { [super setDelegate:(id)aDelegate]; - + delegate = aDelegate; - + _delegateRespondsToShouldBeginEditing = [delegate respondsToSelector:@selector(egoTextViewShouldBeginEditing:)]; _delegateRespondsToShouldEndEditing = [delegate respondsToSelector:@selector(egoTextViewShouldEndEditing:)]; _delegateRespondsToDidBeginEditing = [delegate respondsToSelector:@selector(egoTextViewDidBeginEditing:)]; @@ -413,32 +419,48 @@ - (void)setDelegate:(id)aDelegate { _delegateRespondsToDidChange = [delegate respondsToSelector:@selector(egoTextViewDidChange:)]; _delegateRespondsToDidChangeSelection = [delegate respondsToSelector:@selector(egoTextViewDidChangeSelection:)]; _delegateRespondsToDidSelectURL = [delegate respondsToSelector:@selector(egoTextView:didSelectURL:)]; - + +} + +- (void)setCorrectable:(BOOL)correctable { + if (!_editable) + return; + + if (correctable) { + if (!_textChecker) + _textChecker = [[UITextChecker alloc] init]; + } else { + if (_textChecker!=nil) { + [_textChecker release], + _textChecker=nil; + } + } + _correctable = correctable; } - (void)setEditable:(BOOL)editable { - + if (editable) { - + if (_caretView==nil) { _caretView = [[EGOCaretView alloc] initWithFrame:CGRectZero]; } - + _tokenizer = [[UITextInputStringTokenizer alloc] initWithTextInput:self]; _textChecker = [[UITextChecker alloc] init]; _mutableAttributedString = [[NSMutableAttributedString alloc] init]; - + NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:(int)(kCTUnderlineStyleThick|kCTUnderlinePatternDot)], kCTUnderlineStyleAttributeName, (id)[UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f].CGColor, kCTUnderlineColorAttributeName, nil]; self.correctionAttributes = dictionary; [dictionary release]; - + } else { - + if (_caretView) { [_caretView removeFromSuperview]; [_caretView release], _caretView=nil; } - + self.correctionAttributes=nil; if (_textChecker!=nil) { [_textChecker release], _textChecker=nil; @@ -449,10 +471,10 @@ - (void)setEditable:(BOOL)editable { if (_mutableAttributedString!=nil) { [_mutableAttributedString release], _mutableAttributedString=nil; } - + } _editable = editable; - + } @@ -464,34 +486,34 @@ - (void)setEditable:(BOOL)editable { - (NSRange)rangeIntersection:(NSRange)first withSecond:(NSRange)second { NSRange result = NSMakeRange(NSNotFound, 0); - + if (first.location > second.location) { NSRange tmp = first; first = second; second = tmp; } - + if (second.location < first.location + first.length) { result.location = second.location; NSUInteger end = MIN(first.location + first.length, second.location + second.length); result.length = end - result.location; } - - return result; + + return result; } - (void)drawPathFromRects:(NSArray*)array cornerRadius:(CGFloat)cornerRadius { - + if (array==nil || [array count] == 0) return; - + CGMutablePathRef _path = CGPathCreateMutable(); - + CGRect firstRect = CGRectFromString([array lastObject]); - CGRect lastRect = CGRectFromString([array objectAtIndex:0]); + CGRect lastRect = CGRectFromString([array objectAtIndex:0]); if ([array count]>1) { lastRect.size.width = _textContentView.bounds.size.width-lastRect.origin.x; } - + if (cornerRadius>0) { CGPathAddPath(_path, NULL, [UIBezierPath bezierPathWithRoundedRect:firstRect cornerRadius:cornerRadius].CGPath); CGPathAddPath(_path, NULL, [UIBezierPath bezierPathWithRoundedRect:lastRect cornerRadius:cornerRadius].CGPath); @@ -499,18 +521,18 @@ - (void)drawPathFromRects:(NSArray*)array cornerRadius:(CGFloat)cornerRadius { CGPathAddRect(_path, NULL, firstRect); CGPathAddRect(_path, NULL, lastRect); } - + if ([array count] > 1) { - + CGRect fillRect = CGRectZero; - + CGFloat originX = ([array count]==2) ? MIN(CGRectGetMinX(firstRect), CGRectGetMinX(lastRect)) : 0.0f; CGFloat originY = firstRect.origin.y + firstRect.size.height; CGFloat width = ([array count]==2) ? originX+MIN(CGRectGetMaxX(firstRect), CGRectGetMaxX(lastRect)) : _textContentView.bounds.size.width; CGFloat height = MAX(0.0f, lastRect.origin.y-originY); - + fillRect = CGRectMake(originX, originY, width, height); - + if (cornerRadius>0) { CGPathAddPath(_path, NULL, [UIBezierPath bezierPathWithRoundedRect:fillRect cornerRadius:cornerRadius].CGPath); } else { @@ -518,7 +540,7 @@ - (void)drawPathFromRects:(NSArray*)array cornerRadius:(CGFloat)cornerRadius { } } - + CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextAddPath(ctx, _path); CGContextFillPath(ctx); @@ -527,51 +549,51 @@ - (void)drawPathFromRects:(NSArray*)array cornerRadius:(CGFloat)cornerRadius { } - (void)drawBoundingRangeAsSelection:(NSRange)selectionRange cornerRadius:(CGFloat)cornerRadius { - + if (selectionRange.length == 0 || selectionRange.location == NSNotFound) { return; } - + NSMutableArray *pathRects = [[NSMutableArray alloc] init]; NSArray *lines = (NSArray*)CTFrameGetLines(_frame); CGPoint *origins = (CGPoint*)malloc([lines count] * sizeof(CGPoint)); CTFrameGetLineOrigins(_frame, CFRangeMake(0, [lines count]), origins); NSInteger count = [lines count]; - + for (int i = 0; i < count; i++) { - + CTLineRef line = (CTLineRef) [lines objectAtIndex:i]; CFRange lineRange = CTLineGetStringRange(line); NSRange range = NSMakeRange(lineRange.location==kCFNotFound ? NSNotFound : lineRange.location, lineRange.length); NSRange intersection = [self rangeIntersection:range withSecond:selectionRange]; - + if (intersection.location != NSNotFound && intersection.length > 0) { CGFloat xStart = CTLineGetOffsetForStringIndex(line, intersection.location, NULL); CGFloat xEnd = CTLineGetOffsetForStringIndex(line, intersection.location + intersection.length, NULL); - + CGPoint origin = origins[i]; CGFloat ascent, descent; CTLineGetTypographicBounds(line, &ascent, &descent, NULL); - - CGRect selectionRect = CGRectMake(origin.x + 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; } - + [pathRects addObject:NSStringFromCGRect(selectionRect)]; - - } - } - + + } + } + [self drawPathFromRects:pathRects cornerRadius:cornerRadius]; [pathRects release]; free(origins); } -- (void)drawContentInRect:(CGRect)rect { +- (void)drawContentInRect:(CGRect)rect { [[UIColor colorWithRed:0.8f green:0.8f blue:0.8f alpha:1.0f] setFill]; [self drawBoundingRangeAsSelection:_linkRange cornerRadius:2.0f]; @@ -579,21 +601,21 @@ - (void)drawContentInRect:(CGRect)rect { [self drawBoundingRangeAsSelection:self.selectedRange cornerRadius:0.0f]; [[EGOTextView spellingSelectionColor] setFill]; [self drawBoundingRangeAsSelection:self.correctionRange cornerRadius:2.0f]; - + CGPathRef framePath = CTFrameGetPath(_frame); CGRect frameRect = CGPathGetBoundingBox(framePath); - + NSArray *lines = (NSArray*)CTFrameGetLines(_frame); NSInteger count = [lines count]; CGPoint *origins = (CGPoint*)malloc(count * sizeof(CGPoint)); - CTFrameGetLineOrigins(_frame, CFRangeMake(0, count), origins); + CTFrameGetLineOrigins(_frame, CFRangeMake(0, count), origins); CGContextRef ctx = UIGraphicsGetCurrentContext(); for (int i = 0; i < count; i++) { 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++) { @@ -603,10 +625,10 @@ - (void)drawContentInRect:(CGRect)rect { 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(); @@ -614,204 +636,204 @@ - (void)drawContentInRect:(CGRect)rect { } } free(origins); - + } - (NSInteger)closestWhiteSpaceIndexToPoint:(CGPoint)point { - + point = [self convertPoint:point toView:_textContentView]; NSArray *lines = (NSArray*)CTFrameGetLines(_frame); NSInteger count = [lines count]; CGPoint *origins = (CGPoint*)malloc(count * sizeof(CGPoint)); - CTFrameGetLineOrigins(_frame, CFRangeMake(0, count), origins); - + CTFrameGetLineOrigins(_frame, CFRangeMake(0, count), origins); + __block NSRange returnRange = NSMakeRange(_attributedString.length, 0); - + for (int i = 0; i < lines.count; i++) { - + if (point.y > origins[i].y) { - + CTLineRef line = (CTLineRef)[lines objectAtIndex:i]; CFRange cfRange = CTLineGetStringRange(line); NSRange range = NSMakeRange(cfRange.location == kCFNotFound ? NSNotFound : cfRange.location, cfRange.length); 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) break; - + if (index>=_attributedString.length) { returnRange = NSMakeRange(_attributedString.length, 0); break; } - + if (range.length <= 1) { returnRange = NSMakeRange(range.location, 0); break; } - + if (index == range.location) { returnRange = NSMakeRange(range.location, 0); - break; + break; } - - + + if (index >= (range.location+range.length)) { - + if (range.length > 1 && [_attributedString.string characterAtIndex:(range.location+range.length)-1] == '\n') { - + returnRange = NSMakeRange(index-1, 0); break; - + } else { - + returnRange = NSMakeRange(range.location+range.length, 0); break; - + } - + } - + [_attributedString.string enumerateSubstringsInRange:range options:NSStringEnumerationByWords usingBlock:^(NSString *subString, NSRange subStringRange, NSRange enclosingRange, BOOL *stop){ - - + + if (NSLocationInRange(index, enclosingRange)) { - + if (index > (enclosingRange.location+(enclosingRange.length/2))) { - + returnRange = NSMakeRange(subStringRange.location+subStringRange.length, 0); - + } else { - + returnRange = NSMakeRange(subStringRange.location, 0); - + } - + *stop = YES; } - + }]; - + break; - + } } return returnRange.location; } -- (NSInteger)closestIndexToPoint:(CGPoint)point { +- (NSInteger)closestIndexToPoint:(CGPoint)point { point = [self convertPoint:point toView:_textContentView]; NSArray *lines = (NSArray*)CTFrameGetLines(_frame); NSInteger count = [lines count]; CGPoint *origins = (CGPoint*)malloc(count * sizeof(CGPoint)); - CTFrameGetLineOrigins(_frame, CFRangeMake(0, count), origins); + CTFrameGetLineOrigins(_frame, CFRangeMake(0, count), origins); CFIndex index = kCFNotFound; - + for (int i = 0; i < lines.count; i++) { if (point.y > origins[i].y) { CTLineRef line = (CTLineRef)[lines objectAtIndex:i]; CGPoint convertedPoint = CGPointMake(point.x - origins[i].x, point.y - origins[i].y); - index = CTLineGetStringIndexForPosition(line, convertedPoint); + index = CTLineGetStringIndexForPosition(line, convertedPoint); break; } } - + if (index == kCFNotFound) { index = [_attributedString length]; } - + free(origins); return index; - + } - (NSRange)characterRangeAtPoint_:(CGPoint)point { - + __block NSArray *lines = (NSArray*)CTFrameGetLines(_frame); - + CGPoint *origins = (CGPoint*)malloc([lines count] * sizeof(CGPoint)); - CTFrameGetLineOrigins(_frame, CFRangeMake(0, [lines count]), origins); + CTFrameGetLineOrigins(_frame, CFRangeMake(0, [lines count]), origins); __block NSRange returnRange = NSMakeRange(NSNotFound, 0); for (int i = 0; i < lines.count; i++) { - + if (point.y > origins[i].y) { CTLineRef line = (CTLineRef)[lines objectAtIndex:i]; 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); - + [_attributedString.string enumerateSubstringsInRange:range options:NSStringEnumerationByWords usingBlock:^(NSString *subString, NSRange subStringRange, NSRange enclosingRange, BOOL *stop){ - + if (index - subStringRange.location <= subStringRange.length) { returnRange = subStringRange; *stop = YES; } - + }]; - + break; } } - + free(origins); return returnRange; - + } - (NSRange)characterRangeAtIndex:(NSInteger)index { - + __block NSArray *lines = (NSArray*)CTFrameGetLines(_frame); - NSInteger count = [lines count]; + NSInteger count = [lines count]; __block NSRange returnRange = NSMakeRange(NSNotFound, 0); - + for (int i=0; i < count; i++) { - + __block CTLineRef line = (CTLineRef)[lines objectAtIndex:i]; CFRange cfRange = CTLineGetStringRange(line); NSRange range = NSMakeRange(cfRange.location == kCFNotFound ? NSNotFound : cfRange.location, cfRange.length == kCFNotFound ? 0 : cfRange.length); - + if (index >= range.location && index <= range.location+range.length) { - + if (range.length > 1) { - + [_attributedString.string enumerateSubstringsInRange:range options:NSStringEnumerationByWords usingBlock:^(NSString *subString, NSRange subStringRange, NSRange enclosingRange, BOOL *stop){ - + if (index - subStringRange.location <= subStringRange.length) { returnRange = subStringRange; *stop = YES; } - + }]; - + } } } - + return returnRange; - + } -- (CGRect)caretRectForIndex:(NSInteger)index { - +- (CGRect)caretRectForIndex:(NSInteger)index { + NSArray *lines = (NSArray*)CTFrameGetLines(_frame); - + // no text / first index if (_attributedString.length == 0 || index == 0) { CGPoint origin = CGPointMake(CGRectGetMinX(_textContentView.bounds), CGRectGetMaxY(_textContentView.bounds) - self.font.leading); return CGRectMake(origin.x, origin.y, 3, self.font.ascender + fabs(self.font.descender*2)); - } + } // last index is newline if (index == _attributedString.length && [_attributedString.string characterAtIndex:(index - 1)] == '\n' ) { - + CTLineRef line = (CTLineRef)[lines lastObject]; CFRange range = CTLineGetStringRange(line); CGFloat xPos = CTLineGetOffsetForStringIndex(line, range.location, NULL); @@ -823,69 +845,69 @@ - (CGRect)caretRectForIndex:(NSInteger)index { CTFrameGetLineOrigins(_frame, CFRangeMake([lines count]-1, 0), origins); origin = origins[0]; free(origins); - + origin.y -= self.font.leading; - return CGRectMake(origin.x + xPos, floorf(origin.y - descent), 3, ceilf((descent*2) + ascent)); - + return CGRectMake(origin.x + xPos, floorf(origin.y - descent), 3, ceilf((descent*2) + ascent)); + } index = MAX(index, 0); index = MIN(_attributedString.string.length, index); - NSInteger count = [lines count]; + NSInteger count = [lines count]; CGPoint *origins = (CGPoint*)malloc(count * sizeof(CGPoint)); CTFrameGetLineOrigins(_frame, CFRangeMake(0, count), origins); CGRect returnRect = CGRectZero; - + for (int i = 0; i < count; i++) { CTLineRef line = (CTLineRef)[lines objectAtIndex:i]; CFRange cfRange = CTLineGetStringRange(line); - NSRange range = NSMakeRange(range.location == kCFNotFound ? NSNotFound : cfRange.location, cfRange.length); - + NSRange range = NSMakeRange(cfRange.location == kCFNotFound ? NSNotFound : cfRange.location, cfRange.length); + if (index >= range.location && index <= range.location+range.length) { CGFloat ascent, descent, xPos; - xPos = CTLineGetOffsetForStringIndex((CTLineRef)[lines objectAtIndex:i], index, NULL); + xPos = CTLineGetOffsetForStringIndex((CTLineRef)[lines objectAtIndex:i], index, NULL); CTLineGetTypographicBounds(line, &ascent, &descent, NULL); CGPoint origin = origins[i]; if (_selectedRange.length>0 && index != _selectedRange.location && range.length == 1) { - + xPos = _textContentView.bounds.size.width - 3.0f; // selection of entire line - + } else if ([_attributedString.string characterAtIndex:index-1] == '\n' && range.length == 1) { - + xPos = 0.0f; // empty line } - + returnRect = CGRectMake(origin.x + xPos, floorf(origin.y - descent), 3, ceilf((descent*2) + ascent)); - } - + } + } - + free(origins); return returnRect; } - (CGRect)firstRectForNSRange:(NSRange)range { - + NSInteger index = range.location; - + NSArray *lines = (NSArray *) CTFrameGetLines(_frame); NSInteger count = [lines count]; CGPoint *origins = (CGPoint*)malloc(count * sizeof(CGPoint)); CTFrameGetLineOrigins(_frame, CFRangeMake(0, count), origins); CGRect returnRect = CGRectNull; - + for (int i = 0; i < count; i++) { - + CTLineRef line = (CTLineRef) [lines objectAtIndex:i]; CFRange lineRange = CTLineGetStringRange(line); NSInteger localIndex = index - lineRange.location; - + if (localIndex >= 0 && localIndex < lineRange.length) { NSInteger finalIndex = MIN(lineRange.location + lineRange.length, range.location + range.length); @@ -894,12 +916,12 @@ - (CGRect)firstRectForNSRange:(NSRange)range { CGPoint origin = origins[i]; CGFloat ascent, descent; CTLineGetTypographicBounds(line, &ascent, &descent, NULL); - + returnRect = [_textContentView convertRect:CGRectMake(origin.x + xStart, origin.y - descent, xEnd - xStart, ascent + (descent*2)) toView:self]; break; } } - + free(origins); return returnRect; } @@ -911,15 +933,15 @@ - (CGRect)firstRectForNSRange:(NSRange)range { ///////////////////////////////////////////////////////////////////////////// - (void)selectionChanged { - + if (!_editing) { [_caretView removeFromSuperview]; } - + _ignoreSelectionMenu = NO; - + if (self.selectedRange.length == 0) { - + if (_selectionView!=nil) { [_selectionView removeFromSuperview]; _selectionView=nil; @@ -927,48 +949,58 @@ - (void)selectionChanged { if (!_caretView.superview) { [_textContentView addSubview:_caretView]; - [_textContentView setNeedsDisplay]; + [_textContentView setNeedsDisplay]; } _caretView.frame = [self caretRectForIndex:self.selectedRange.location]; [_caretView delayBlink]; - + CGRect frame = _caretView.frame; frame.origin.y -= (self.font.lineHeight*2); [self scrollRectToVisible:[_textContentView convertRect:frame toView:self] animated:YES]; - + [_textContentView setNeedsDisplay]; - + _longPress.minimumPressDuration = 0.5f; - + } else { - + _longPress.minimumPressDuration = 0.0f; - + if ((_caretView!=nil) && _caretView.superview) { [_caretView removeFromSuperview]; } - + if (_selectionView==nil) { - + EGOSelectionView *view = [[EGOSelectionView alloc] initWithFrame:_textContentView.bounds]; [_textContentView addSubview:view]; _selectionView=view; - [view release]; - + [view release]; + } - + CGRect begin = [self caretRectForIndex:_selectedRange.location]; CGRect end = [self caretRectForIndex:_selectedRange.location+_selectedRange.length]; [_selectionView setBeginCaret:begin endCaret:end]; [_textContentView setNeedsDisplay]; - - } - + + } + if (self.markedRange.location != NSNotFound) { [_textContentView setNeedsDisplay]; } - + +} + +- (NSRange)checkRange:(NSRange)r +{ + if (r.location == NSNotFound) return r; + if (r.location > self.attributedString.length) + r.location = self.attributedString.length; + if (r.location + r.length > self.attributedString.length) + r.length = self.attributedString.length - r.location; + return r; } - (NSRange)markedRange { @@ -979,13 +1011,15 @@ - (NSRange)selectedRange { return _selectedRange; } -- (void)setMarkedRange:(NSRange)range { - _markedRange = range; +- (void)setMarkedRange:(NSRange)range { + _markedRange = NSMakeRange(range.location == NSNotFound ? NSNotFound : MAX(0, range.location), range.length); + _markedRange = [self checkRange:_markedRange]; //[self selectionChanged]; } - (void)setSelectedRange:(NSRange)range { _selectedRange = NSMakeRange(range.location == NSNotFound ? NSNotFound : MAX(0, range.location), range.length); + _selectedRange = [self checkRange:_selectedRange]; [self selectionChanged]; } @@ -995,43 +1029,43 @@ - (void)setCorrectionRange:(NSRange)range { _correctionRange = range; return; } - + _correctionRange = range; if (range.location != NSNotFound && range.length > 0) { - + if (_caretView.superview) { [_caretView removeFromSuperview]; } - + [self removeCorrectionAttributesForRange:_correctionRange]; [self showCorrectionMenuForRange:_correctionRange]; - + } else { - + if (!_caretView.superview) { [_textContentView addSubview:_caretView]; [_caretView delayBlink]; } - + } - + [_textContentView setNeedsDisplay]; - + } - (void)setLinkRange:(NSRange)range { - + _linkRange = range; - + if (_linkRange.length>0) { - + if (_caretView.superview!=nil) { [_caretView removeFromSuperview]; } - + } else { - + if (_caretView.superview==nil) { if (!_caretView.superview) { [_textContentView addSubview:_caretView]; @@ -1039,28 +1073,28 @@ - (void)setLinkRange:(NSRange)range { [_caretView delayBlink]; } } - + } - + [_textContentView setNeedsDisplay]; } - (void)setLinkRangeFromTextCheckerResults:(NSTextCheckingResult*)results { - + if (_linkRange.length>0) { UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:[[results URL] absoluteString] delegate:(id)self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Open", nil]; [actionSheet showInView:self]; [actionSheet release]; } - + } + (UIColor*)selectionColor { static UIColor *color = nil; if (color == nil) { - color = [[UIColor colorWithRed:0.800f green:0.867f blue:0.929f alpha:1.0f] retain]; - } + color = [[UIColor colorWithRed:0.800f green:0.867f blue:0.929f alpha:1.0f] retain]; + } return color; } @@ -1091,24 +1125,24 @@ + (UIColor*)spellingSelectionColor { - (NSString *)textInRange:(UITextRange *)range { EGOIndexedRange *r = (EGOIndexedRange *)range; - return ([_attributedString.string substringWithRange:r.range]); + return ([self.attributedString.string substringWithRange:[self checkRange:r.range]]); } - (void)replaceRange:(UITextRange *)range withText:(NSString *)text { - + EGOIndexedRange *r = (EGOIndexedRange *)range; NSRange selectedNSRange = self.selectedRange; - if ((r.range.location + r.range.length) <= selectedNSRange.location) { - selectedNSRange.location -= (r.range.length - text.length); - } else { - selectedNSRange = [self rangeIntersection:r.range withSecond:_selectedRange]; - } - - [_mutableAttributedString replaceCharactersInRange:r.range withString:text]; + [_mutableAttributedString replaceCharactersInRange:r.range withString:text]; self.attributedString = _mutableAttributedString; self.selectedRange = selectedNSRange; - + +} + +- (void)replaceNSRange:(NSRange)range withText:(NSString *)text +{ + UITextRange *r = [EGOIndexedRange rangeWithNSRange:range]; + [self replaceRange:r withText:text]; } // MARK: UITextInput - Working with Marked and Selected Text @@ -1123,55 +1157,55 @@ - (void)setSelectedTextRange:(UITextRange *)range { } - (UITextRange *)markedTextRange { - return [EGOIndexedRange rangeWithNSRange:self.markedRange]; + return [EGOIndexedRange rangeWithNSRange:self.markedRange]; } - (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange { - + NSRange selectedNSRange = self.selectedRange; NSRange markedTextRange = self.markedRange; - + if (markedTextRange.location != NSNotFound) { if (!markedText) markedText = @""; - + [_mutableAttributedString replaceCharactersInRange:markedTextRange withString:markedText]; markedTextRange.length = markedText.length; - + } else if (selectedNSRange.length > 0) { - + [_mutableAttributedString replaceCharactersInRange:selectedNSRange withString:markedText]; markedTextRange.location = selectedNSRange.location; markedTextRange.length = markedText.length; - + } else { - + NSAttributedString *string = [[NSAttributedString alloc] initWithString:markedText attributes:self.defaultAttributes]; - [_mutableAttributedString insertAttributedString:string atIndex:selectedNSRange.location]; + [_mutableAttributedString insertAttributedString:string atIndex:selectedNSRange.location]; [string release]; - + markedTextRange.location = selectedNSRange.location; markedTextRange.length = markedText.length; } - + selectedNSRange = NSMakeRange(selectedRange.location + markedTextRange.location, selectedRange.length); - - self.attributedString = _attributedString; + + self.attributedString = _mutableAttributedString; self.markedRange = markedTextRange; - self.selectedRange = selectedNSRange; - + self.selectedRange = selectedNSRange; + } - (void)unmarkText { - + NSRange markedTextRange = self.markedRange; - + if (markedTextRange.location == NSNotFound) return; - + markedTextRange.location = NSNotFound; - self.markedRange = markedTextRange; - + self.markedRange = markedTextRange; + } // MARK: UITextInput - Computing Text Ranges and Text Positions @@ -1187,20 +1221,20 @@ - (UITextPosition*)endOfDocument { - (UITextRange*)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition { EGOIndexedPosition *from = (EGOIndexedPosition *)fromPosition; - EGOIndexedPosition *to = (EGOIndexedPosition *)toPosition; + EGOIndexedPosition *to = (EGOIndexedPosition *)toPosition; NSRange range = NSMakeRange(MIN(from.index, to.index), ABS(to.index - from.index)); - return [EGOIndexedRange rangeWithNSRange:range]; - + return [EGOIndexedRange rangeWithNSRange:range]; + } - (UITextPosition*)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset { - EGOIndexedPosition *pos = (EGOIndexedPosition *)position; + EGOIndexedPosition *pos = (EGOIndexedPosition *)position; NSInteger end = pos.index + offset; - + if (end > _attributedString.length || end < 0) return nil; - + return [EGOIndexedPosition positionWithIndex:end]; } @@ -1208,7 +1242,7 @@ - (UITextPosition*)positionFromPosition:(UITextPosition *)position inDirection:( EGOIndexedPosition *pos = (EGOIndexedPosition *)position; NSInteger newPos = pos.index; - + switch (direction) { case UITextLayoutDirectionRight: newPos += offset; @@ -1217,20 +1251,20 @@ - (UITextPosition*)positionFromPosition:(UITextPosition *)position inDirection:( newPos -= offset; break; UITextLayoutDirectionUp: // not supported right now - break; + break; UITextLayoutDirectionDown: // not supported right now break; default: break; } - + if (newPos < 0) newPos = 0; - + if (newPos > _attributedString.length) newPos = _attributedString.length; - + return [EGOIndexedPosition positionWithIndex:newPos]; } @@ -1239,7 +1273,7 @@ - (UITextPosition*)positionFromPosition:(UITextPosition *)position inDirection:( - (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other { EGOIndexedPosition *pos = (EGOIndexedPosition *)position; EGOIndexedPosition *o = (EGOIndexedPosition *)other; - + if (pos.index == o.index) { return NSOrderedSame; } if (pos.index < o.index) { @@ -1268,38 +1302,38 @@ - (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection EGOIndexedRange *r = (EGOIndexedRange *)range; NSInteger pos = r.range.location; - + switch (direction) { case UITextLayoutDirectionUp: case UITextLayoutDirectionLeft: pos = r.range.location; break; case UITextLayoutDirectionRight: - case UITextLayoutDirectionDown: + case UITextLayoutDirectionDown: pos = r.range.location + r.range.length; break; } - - return [EGOIndexedPosition positionWithIndex:pos]; + + return [EGOIndexedPosition positionWithIndex:pos]; } - (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction { EGOIndexedPosition *pos = (EGOIndexedPosition *)position; NSRange result = NSMakeRange(pos.index, 1); - + switch (direction) { case UITextLayoutDirectionUp: case UITextLayoutDirectionLeft: result = NSMakeRange(pos.index - 1, 1); break; case UITextLayoutDirectionRight: - case UITextLayoutDirectionDown: + case UITextLayoutDirectionDown: result = NSMakeRange(pos.index, 1); break; } - - return [EGOIndexedRange rangeWithNSRange:result]; + + return [EGOIndexedRange rangeWithNSRange:result]; } - (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction { @@ -1313,15 +1347,15 @@ - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRang // MARK: UITextInput - Geometry - (CGRect)firstRectForRange:(UITextRange *)range { - - EGOIndexedRange *r = (EGOIndexedRange *)range; - return [self firstRectForNSRange:r.range]; + + EGOIndexedRange *r = (EGOIndexedRange *)range; + return [self firstRectForNSRange:r.range]; } - (CGRect)caretRectForPosition:(UITextPosition *)position { - + EGOIndexedPosition *pos = (EGOIndexedPosition *)position; - return [self caretRectForIndex:pos.index]; + return [self caretRectForIndex:pos.index]; } - (UIView *)textInputView { @@ -1331,24 +1365,24 @@ - (UIView *)textInputView { // MARK: UITextInput - Hit testing - (UITextPosition*)closestPositionToPoint:(CGPoint)point { - + EGOIndexedPosition *position = [EGOIndexedPosition positionWithIndex:[self closestIndexToPoint:point]]; return position; - + } - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range { - + EGOIndexedPosition *position = [EGOIndexedPosition positionWithIndex:[self closestIndexToPoint:point]]; return position; - + } - (UITextRange*)characterRangeAtPoint:(CGPoint)point { - + EGOIndexedRange *range = [EGOIndexedRange rangeWithNSRange:[self characterRangeAtPoint_:point]]; return range; - + } // MARK: UITextInput - Styling Information @@ -1358,15 +1392,15 @@ - (NSDictionary*)textStylingAtPosition:(UITextPosition *)position inDirection:(U EGOIndexedPosition *pos = (EGOIndexedPosition*)position; NSInteger index = MAX(pos.index, 0); index = MIN(index, _attributedString.length-1); - + NSDictionary *attribs = [self.attributedString attributesAtIndex:index effectiveRange:nil]; NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:1]; - + CTFontRef ctFont = (CTFontRef)[attribs valueForKey:(NSString*)kCTFontAttributeName]; UIFont *font = [UIFont fontWithName:(NSString*)CTFontCopyFamilyName(ctFont) size:CTFontGetSize(ctFont)]; - + [dictionary setObject:font forKey:UITextInputTextFontKey]; - + return dictionary; } @@ -1382,112 +1416,106 @@ - (BOOL)hasText { } - (void)insertText:(NSString *)text { - + NSRange selectedNSRange = self.selectedRange; NSRange markedTextRange = self.markedRange; - [_mutableAttributedString setAttributedString:self.attributedString]; - NSAttributedString *newString = [[NSAttributedString alloc] initWithString:text attributes:self.defaultAttributes]; - + if (_correctionRange.location != NSNotFound && _correctionRange.length > 0){ - + [_mutableAttributedString replaceCharactersInRange:self.correctionRange withAttributedString:newString]; selectedNSRange.length = 0; selectedNSRange.location = (self.correctionRange.location+text.length); self.correctionRange = NSMakeRange(NSNotFound, 0); } else if (markedTextRange.location != NSNotFound) { - + [_mutableAttributedString replaceCharactersInRange:markedTextRange withAttributedString:newString]; selectedNSRange.location = markedTextRange.location + text.length; selectedNSRange.length = 0; - markedTextRange = NSMakeRange(NSNotFound, 0); - + markedTextRange = NSMakeRange(NSNotFound, 0); + } else if (selectedNSRange.length > 0) { - + [_mutableAttributedString replaceCharactersInRange:selectedNSRange withAttributedString:newString]; selectedNSRange.length = 0; selectedNSRange.location = (selectedNSRange.location + text.length); - + } else { - - [_mutableAttributedString insertAttributedString:newString atIndex:selectedNSRange.location]; + + [_mutableAttributedString insertAttributedString:newString atIndex:selectedNSRange.location]; selectedNSRange.location += text.length; - + } - + [newString release]; - + self.attributedString = _mutableAttributedString; self.markedRange = markedTextRange; - self.selectedRange = selectedNSRange; - + self.selectedRange = selectedNSRange; + if (text.length > 1 || ([text isEqualToString:@" "] || [text isEqualToString:@"\n"])) { [self checkSpellingForRange:[self characterRangeAtIndex:self.selectedRange.location-1]]; [self checkLinksForRange:NSMakeRange(0, self.attributedString.length)]; } - } - (void)deleteBackward { - + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showCorrectionMenuWithoutSelection) object:nil]; - - NSRange selectedNSRange = self.selectedRange; - NSRange markedTextRange = self.markedRange; - + [_mutableAttributedString setAttributedString:self.attributedString]; + NSRange selectedNSRange = [self checkRange:self.selectedRange]; + NSRange markedTextRange = [self checkRange:self.markedRange];; + if ((_correctionRange.location > selectedNSRange.location + selectedNSRange.length) || (selectedNSRange.location > _correctionRange.location + _correctionRange.length)) { + _correctionRange.location = NSNotFound; + _correctionRange.length = 0; + } if (_correctionRange.location != NSNotFound && _correctionRange.length > 0) { - [_mutableAttributedString beginEditing]; [_mutableAttributedString deleteCharactersInRange:self.correctionRange]; [_mutableAttributedString endEditing]; self.correctionRange = NSMakeRange(NSNotFound, 0); selectedNSRange.length = 0; - + } else if (markedTextRange.location != NSNotFound) { - [_mutableAttributedString beginEditing]; [_mutableAttributedString deleteCharactersInRange:selectedNSRange]; [_mutableAttributedString endEditing]; - - selectedNSRange.location = markedTextRange.location; + + //selectedNSRange.location = markedTextRange.location; selectedNSRange.length = 0; markedTextRange = NSMakeRange(NSNotFound, 0); - + } else if (selectedNSRange.length > 0) { - [_mutableAttributedString beginEditing]; [_mutableAttributedString deleteCharactersInRange:selectedNSRange]; [_mutableAttributedString endEditing]; - + selectedNSRange.length = 0; - + } else if (selectedNSRange.location > 0) { - NSInteger index = MAX(0, selectedNSRange.location-1); index = MIN(_attributedString.length-1, index); if ([_attributedString.string characterAtIndex:index] == ' ') { [self performSelector:@selector(showCorrectionMenuWithoutSelection) withObject:nil afterDelay:0.2f]; } - + selectedNSRange.location--; selectedNSRange.length = 1; - + [_mutableAttributedString beginEditing]; [_mutableAttributedString deleteCharactersInRange:selectedNSRange]; [_mutableAttributedString endEditing]; - + selectedNSRange.length = 0; - } - + self.attributedString = _mutableAttributedString; self.markedRange = markedTextRange; - self.selectedRange = selectedNSRange; - + self.selectedRange = selectedNSRange; } @@ -1497,32 +1525,32 @@ - (void)deleteBackward { ///////////////////////////////////////////////////////////////////////////// - (NSTextCheckingResult*)linkAtIndex:(NSInteger)index { - + NSRange range = [self characterRangeAtIndex:index]; if (range.location==NSNotFound || range.length == 0) { return nil; } - + __block NSTextCheckingResult *link = nil; NSError *error = nil; NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error]; [linkDetector enumerateMatchesInString:[self.attributedString string] options:0 range:range usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { - + if ([result resultType] == NSTextCheckingTypeLink) { *stop = YES; link = [result retain]; } - + }]; return [link autorelease]; - + } - (void)checkLinksForRange:(NSRange)range { - + NSDictionary *linkAttributes = [NSDictionary dictionaryWithObjectsAndKeys:(id)[UIColor blueColor].CGColor, kCTForegroundColorAttributeName, [NSNumber numberWithInt:(int)kCTUnderlineStyleSingle], kCTUnderlineStyleAttributeName, nil]; - + NSMutableAttributedString *string = [_attributedString mutableCopy]; NSError *error = nil; NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error]; @@ -1533,24 +1561,24 @@ - (void)checkLinksForRange:(NSRange)range { } }]; - + if (![self.attributedString isEqualToAttributedString:string]) { self.attributedString = string; } - + } - (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, @@ -1558,14 +1586,14 @@ - (void)scanAttachments { //.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; @@ -1573,22 +1601,22 @@ - (void)scanAttachments { } - (BOOL)selectedLinkAtIndex:(NSInteger)index { - + NSTextCheckingResult *_link = [self linkAtIndex:index]; if (_link!=nil) { [self setLinkRange:[_link range]]; return YES; } - + return NO; } - (void)openLink:(NSURL*)aURL { - + [[UIApplication sharedApplication] openURL:aURL]; - + //self. - + } @@ -1598,27 +1626,27 @@ - (void)openLink:(NSURL*)aURL { ///////////////////////////////////////////////////////////////////////////// - (void)insertCorrectionAttributesForRange:(NSRange)range { - + NSMutableAttributedString *string = [_attributedString mutableCopy]; [string addAttributes:self.correctionAttributes range:range]; self.attributedString = string; [string release]; - + } - (void)removeCorrectionAttributesForRange:(NSRange)range { - + NSMutableAttributedString *string = [_attributedString mutableCopy]; [string removeAttribute:(NSString*)kCTUnderlineStyleAttributeName range:range]; self.attributedString = string; [string release]; - + } - (void)checkSpellingForRange:(NSRange)range { - + if (!self.correctable) return; [_mutableAttributedString setAttributedString:self.attributedString]; - + NSInteger location = range.location-1; NSInteger currentOffset = MAX(0, location); NSRange currentRange; @@ -1626,35 +1654,35 @@ - (void)checkSpellingForRange:(NSRange)range { NSRange stringRange = NSMakeRange(0, string.length-1); NSArray *guesses; BOOL done = NO; - + NSString *language = [[UITextChecker availableLanguages] objectAtIndex:0]; if (!language) { language = @"en_US"; } - + while (!done) { - + currentRange = [_textChecker rangeOfMisspelledWordInString:string range:stringRange startingAt:currentOffset wrap:NO language:language]; - + if (currentRange.location == NSNotFound || currentRange.location > range.location) { done = YES; continue; } guesses = [_textChecker guessesForWordRange:currentRange inString:string language:language]; - - if (guesses!=nil) { + + if (guesses!=nil) { [_mutableAttributedString addAttributes:self.correctionAttributes range:currentRange]; } - + currentOffset = currentOffset + (currentRange.length-1); - + } - + if (![self.attributedString isEqualToAttributedString:_mutableAttributedString]) { self.attributedString = _mutableAttributedString; } - + } @@ -1664,11 +1692,11 @@ - (void)checkSpellingForRange:(NSRange)range { ///////////////////////////////////////////////////////////////////////////// - (EGOTextWindow*)egoTextWindow { - + if (_textWindow==nil) { - + EGOTextWindow *window = nil; - + for (EGOTextWindow *aWindow in [[UIApplication sharedApplication] windows]){ if ([aWindow isKindOfClass:[EGOTextWindow class]]) { window = aWindow; @@ -1676,139 +1704,139 @@ - (EGOTextWindow*)egoTextWindow { break; } } - + if (window==nil) { window = [[EGOTextWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; } - + window.windowLevel = UIWindowLevelStatusBar; window.hidden = NO; _textWindow=window; - + } - + return _textWindow; - + } - (void)longPress:(UILongPressGestureRecognizer*)gesture { if (gesture.state==UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) { - + if (_linkRange.length>0 && gesture.state == UIGestureRecognizerStateBegan) { NSTextCheckingResult *link = [self linkAtIndex:_linkRange.location]; [self setLinkRangeFromTextCheckerResults:link]; gesture.enabled=NO; gesture.enabled=YES; } - - + + UIMenuController *menuController = [UIMenuController sharedMenuController]; if ([menuController isMenuVisible]) { [menuController setMenuVisible:NO animated:NO]; } - + CGPoint point = [gesture locationInView:self]; BOOL _selection = (_selectionView!=nil); if (!_selection && _caretView!=nil) { [_caretView show]; } - + _textWindow = [self egoTextWindow]; [_textWindow updateWindowTransform]; [_textWindow setType:_selection ? EGOWindowMagnify : EGOWindowLoupe]; point.y -= 20.0f; NSInteger index = [self closestIndexToPoint:point]; - + if (_selection) { - + if (gesture.state == UIGestureRecognizerStateBegan) { _textWindow.selectionType = (index > (_selectedRange.location+(_selectedRange.length/2))) ? EGOSelectionTypeRight : EGOSelectionTypeLeft; } - + CGRect rect = CGRectZero; if (_textWindow.selectionType==EGOSelectionTypeLeft) { - + NSInteger begin = MAX(0, index); begin = MIN(_selectedRange.location+_selectedRange.length-1, begin); - + NSInteger end = _selectedRange.location + _selectedRange.length; end = MIN(_attributedString.string.length, end-begin); - + self.selectedRange = NSMakeRange(begin, end); index = _selectedRange.location; - + } else { - + NSInteger length = MIN(index-_selectedRange.location, _attributedString.string.length-_selectedRange.location); - length = MAX(1, length); + length = MAX(1, length); self.selectedRange = NSMakeRange(self.selectedRange.location, length); - index = (_selectedRange.location+_selectedRange.length); - + index = (_selectedRange.location+_selectedRange.length); + } - + rect = [self caretRectForIndex:index]; - + if (gesture.state == UIGestureRecognizerStateBegan) { - + [_textWindow showFromView:_textContentView rect:[_textContentView convertRect:rect toView:_textWindow]]; } else { - + [_textWindow renderWithContentView:_textContentView fromRect:[_textContentView convertRect:rect toView:_textWindow]]; - + } - + } else { - + CGPoint location = [gesture locationInView:_textWindow]; CGRect rect = CGRectMake(location.x, location.y, _caretView.bounds.size.width, _caretView.bounds.size.height); - + self.selectedRange = NSMakeRange(index, 0); - + if (gesture.state == UIGestureRecognizerStateBegan) { - + [_textWindow showFromView:_textContentView rect:rect]; - + } else { - + [_textWindow renderWithContentView:_textContentView fromRect:rect]; - + } - + } - + } else { - + if (_caretView!=nil) { [_caretView delayBlink]; } - + if ((_textWindow!=nil)) { [_textWindow hide:YES]; _textWindow=nil; } - + if (gesture.state == UIGestureRecognizerStateEnded) { if (self.selectedRange.location!=NSNotFound && self.selectedRange.length>0) { [self showMenu]; } } } - + } - (void)doubleTap:(UITapGestureRecognizer*)gesture { - + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showMenu) object:nil]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showCorrectionMenu) object:nil]; NSInteger index = [self closestWhiteSpaceIndexToPoint:[gesture locationInView:self]]; NSRange range = [self characterRangeAtIndex:index]; if (range.location!=NSNotFound && range.length>0) { - + [self.inputDelegate selectionWillChange:self]; self.selectedRange = range; [self.inputDelegate selectionDidChange:self]; @@ -1816,40 +1844,40 @@ - (void)doubleTap:(UITapGestureRecognizer*)gesture { if (![[UIMenuController sharedMenuController] isMenuVisible]) { [self performSelector:@selector(showMenu) withObject:nil afterDelay:0.1f]; } - } - + } + } - (void)tap:(UITapGestureRecognizer*)gesture { - + if (_editable && ![self isFirstResponder]) { - [self becomeFirstResponder]; + [self becomeFirstResponder]; return; } - + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showMenu) object:nil]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showCorrectionMenu) object:nil]; - + self.correctionRange = NSMakeRange(NSNotFound, 0); if (self.selectedRange.length>0) { self.selectedRange = NSMakeRange(_selectedRange.location, 0); } - + NSInteger index = [self closestWhiteSpaceIndexToPoint:[gesture locationInView:self]]; - + if (_delegateRespondsToDidSelectURL && !_editing) { if ([self selectedLinkAtIndex:index]) { return; } } - + UIMenuController *menuController = [UIMenuController sharedMenuController]; if ([menuController isMenuVisible]) { [menuController setMenuVisible:NO animated:NO]; - + } else { - + if (index==self.selectedRange.location) { [self performSelector:@selector(showMenu) withObject:nil afterDelay:0.35f]; } else { @@ -1857,16 +1885,16 @@ - (void)tap:(UITapGestureRecognizer*)gesture { [self performSelector:@selector(showCorrectionMenu) withObject:nil afterDelay:0.35f]; } } - + } - + [self.inputDelegate selectionWillChange:self]; - + self.markedRange = NSMakeRange(NSNotFound, 0); self.selectedRange = NSMakeRange(index, 0); - + [self.inputDelegate selectionDidChange:self]; - + } @@ -1876,30 +1904,30 @@ - (void)tap:(UITapGestureRecognizer*)gesture { ///////////////////////////////////////////////////////////////////////////// - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{ - + if ([gestureRecognizer isKindOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")]) { UIMenuController *menuController = [UIMenuController sharedMenuController]; if ([menuController isMenuVisible]) { [menuController setMenuVisible:NO animated:NO]; } } - + return NO; - + } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{ - + if (gestureRecognizer==_longPress) { - - if (_selectedRange.length>0 && _selectionView!=nil) { + + if (_selectedRange.length>0 && _selectionView!=nil) { return CGRectContainsPoint(CGRectInset([_textContentView convertRect:_selectionView.frame toView:self], -20.0f, -20.0f) , [gestureRecognizer locationInView:self]); } - + } - + return YES; - + } @@ -1909,21 +1937,21 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{ ///////////////////////////////////////////////////////////////////////////// - (void)actionSheet:(UIActionSheet *)actionSheet willDismissWithButtonIndex:(NSInteger)buttonIndex { - + if (actionSheet.cancelButtonIndex != buttonIndex) { - + if (_delegateRespondsToDidChange) { [self.delegate egoTextView:self didSelectURL:[NSURL URLWithString:actionSheet.title]]; } else { [self openLink:[NSURL URLWithString:actionSheet.title]]; } - + } else { - + [self becomeFirstResponder]; - + } - + [self setLinkRange:NSMakeRange(NSNotFound, 0)]; } @@ -1939,20 +1967,20 @@ - (BOOL)canBecomeFirstResponder { if (_editable && _delegateRespondsToShouldBeginEditing) { return [self.delegate egoTextViewShouldBeginEditing:self]; } - + return YES; } - (BOOL)becomeFirstResponder { if (_editable) { - + _editing = YES; if (_delegateRespondsToDidBeginEditing) { [self.delegate egoTextViewDidBeginEditing:self]; } - + [self selectionChanged]; } @@ -1960,30 +1988,30 @@ - (BOOL)becomeFirstResponder { } - (BOOL)canResignFirstResponder { - + if (_editable && _delegateRespondsToShouldEndEditing) { return [self.delegate egoTextViewShouldEndEditing:self]; } - + return YES; } - (BOOL)resignFirstResponder { if (_editable) { - - _editing = NO; + + _editing = NO; if (_delegateRespondsToDidEndEditing) { [self.delegate egoTextViewDidEndEditing:self]; } - + [self selectionChanged]; - + } return [super resignFirstResponder]; - + } @@ -1993,117 +2021,117 @@ - (BOOL)resignFirstResponder { ///////////////////////////////////////////////////////////////////////////// - (CGRect)menuPresentationRect { - + CGRect rect = [_textContentView convertRect:_caretView.frame toView:self]; - + if (_selectedRange.location != NSNotFound && _selectedRange.length > 0) { - + if (_selectionView!=nil) { rect = [_textContentView convertRect:_selectionView.frame toView:self]; } else { rect = [self firstRectForNSRange:_selectedRange]; } - + } else if (_editing && _correctionRange.location != NSNotFound && _correctionRange.length > 0) { - + rect = [self firstRectForNSRange:_correctionRange]; - - } - + + } + return rect; - + } - (void)showMenu { - + UIMenuController *menuController = [UIMenuController sharedMenuController]; - + if ([menuController isMenuVisible]) { - [menuController setMenuVisible:NO animated:NO]; + [menuController setMenuVisible:NO animated:NO]; } - + dispatch_async(dispatch_get_main_queue(), ^{ [menuController setMenuItems:nil]; [menuController setTargetRect:[self menuPresentationRect] inView:self]; [menuController update]; - [menuController setMenuVisible:YES animated:YES]; + [menuController setMenuVisible:YES animated:YES]; }); - - - + + + } - (void)showCorrectionMenu { - + if (_editing) { - + NSRange range = [self characterRangeAtIndex:self.selectedRange.location]; if (range.location!=NSNotFound && range.length>1) { - + NSString *language = [[UITextChecker availableLanguages] objectAtIndex:0]; if (!language) language = @"en_US"; self.correctionRange = [_textChecker rangeOfMisspelledWordInString:_attributedString.string range:range startingAt:0 wrap:YES language:language]; - + } } - + } - (void)showCorrectionMenuWithoutSelection { - + if (_editing) { - + NSRange range = [self characterRangeAtIndex:self.selectedRange.location]; [self showCorrectionMenuForRange:range]; - + } else { - + [self showMenu]; - + } - + } - (void)showCorrectionMenuForRange:(NSRange)range { - + if (range.location==NSNotFound || range.length==0) return; - + range.location = MAX(0, range.location); range.length = MIN(_attributedString.string.length, range.length); - + [self removeCorrectionAttributesForRange:range]; - + UIMenuController *menuController = [UIMenuController sharedMenuController]; - + if ([menuController isMenuVisible]) return; _ignoreSelectionMenu = YES; - + NSString *language = [[UITextChecker availableLanguages] objectAtIndex:0]; if (!language) { language = @"en_US"; - } - + } + NSArray *guesses = [_textChecker guessesForWordRange:range inString:_attributedString.string language:language]; - + [menuController setTargetRect:[self menuPresentationRect] inView:self]; - + if (guesses!=nil && [guesses count]>0) { - + NSMutableArray *items = [[NSMutableArray alloc] init]; - + if (self.menuItemActions==nil) { self.menuItemActions = [NSMutableDictionary dictionary]; } - + for (NSString *word in guesses){ - - NSString *selString = [NSString stringWithFormat:@"spellCheckMenu_%i:", [word hash]]; + + NSString *selString = [NSString stringWithFormat:@"spellCheckMenu_%lu:", (unsigned long)[word hash]]; SEL sel = sel_registerName([selString UTF8String]); - - [self.menuItemActions setObject:word forKey:NSStringFromSelector(sel)]; + + [self.menuItemActions setObject:word forKey:NSStringFromSelector(sel)]; class_addMethod([self class], sel, [[self class] instanceMethodForSelector:@selector(spellingCorrection:)], "v@:@"); - + UIMenuItem *item = [[UIMenuItem alloc] initWithTitle:word action:sel]; [items addObject:item]; [item release]; @@ -2111,22 +2139,22 @@ - (void)showCorrectionMenuForRange:(NSRange)range { break; } } - - [menuController setMenuItems:items]; + + [menuController setMenuItems:items]; [items release]; - - - + + + } else { - + UIMenuItem *item = [[UIMenuItem alloc] initWithTitle:@"No Replacements Found" action:@selector(spellCheckMenuEmpty:)]; [menuController setMenuItems:[NSArray arrayWithObject:item]]; - [item release]; - + [item release]; + } - + [menuController setMenuVisible:YES animated:YES]; - + } @@ -2136,7 +2164,7 @@ - (void)showCorrectionMenuForRange:(NSRange)range { ///////////////////////////////////////////////////////////////////////////// - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { - + if (self.correctionRange.length>0 || _ignoreSelectionMenu) { if ([NSStringFromSelector(action) hasPrefix:@"spellCheckMenu"]) { return YES; @@ -2157,25 +2185,25 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { } return [super canPerformAction:action withSender:sender]; - + } - (void)spellingCorrection:(UIMenuController*)sender { - + NSRange replacementRange = _correctionRange; - + if (replacementRange.location==NSNotFound || replacementRange.length==0) { replacementRange = [self characterRangeAtIndex:self.selectedRange.location]; } if (replacementRange.location!=NSNotFound && replacementRange.length!=0) { NSString *text = [self.menuItemActions objectForKey:NSStringFromSelector(_cmd)]; - [self.inputDelegate textWillChange:self]; + [self.inputDelegate textWillChange:self]; [self replaceRange:[EGOIndexedRange rangeWithNSRange:replacementRange] withText:text]; - [self.inputDelegate textDidChange:self]; + [self.inputDelegate textDidChange:self]; replacementRange.length = text.length; [self removeCorrectionAttributesForRange:replacementRange]; } - + self.correctionRange = NSMakeRange(NSNotFound, 0); self.menuItemActions = nil; [sender setMenuItems:nil]; @@ -2185,85 +2213,85 @@ - (void)spellingCorrection:(UIMenuController*)sender { - (void)spellCheckMenuEmpty:(id)sender { self.correctionRange = NSMakeRange(NSNotFound, 0); - + } - (void)menuDidHide:(NSNotification*)notification { - + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerDidHideMenuNotification object:nil]; - + if (_selectionView) { [self showMenu]; } } - (void)paste:(id)sender { - + NSString *pasteText = [[UIPasteboard generalPasteboard] valueForPasteboardType:@"public.utf8-plain-text"]; - + if (pasteText!=nil) { [self insertText:pasteText]; } - + } - (void)selectAll:(id)sender { - + NSString *string = [_attributedString string]; NSString *trimmedString = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; self.selectedRange = [_attributedString.string rangeOfString:trimmedString]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuDidHide:) name:UIMenuControllerDidHideMenuNotification object:nil]; } - (void)select:(id)sender { - + NSRange range = [self characterRangeAtPoint_:_caretView.center]; self.selectedRange = range; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuDidHide:) name:UIMenuControllerDidHideMenuNotification object:nil]; } - (void)cut:(id)sender { - - NSString *string = [_attributedString.string substringWithRange:_selectedRange]; + + NSString *string = [self.attributedString.string substringWithRange:self.selectedRange]; [[UIPasteboard generalPasteboard] setValue:string forPasteboardType:@"public.utf8-plain-text"]; - + [_mutableAttributedString setAttributedString:self.attributedString]; [_mutableAttributedString deleteCharactersInRange:_selectedRange]; - - [self.inputDelegate textWillChange:self]; + + [self.inputDelegate textWillChange:self]; [self setAttributedString:_mutableAttributedString]; - [self.inputDelegate textDidChange:self]; - + [self.inputDelegate textDidChange:self]; + self.selectedRange = NSMakeRange(_selectedRange.location, 0); - + } - (void)copy:(id)sender { - - NSString *string = [self.attributedString.string substringWithRange:_selectedRange]; + + NSString *string = [self.attributedString.string substringWithRange:self.selectedRange]; [[UIPasteboard generalPasteboard] setValue:string forPasteboardType:@"public.utf8-plain-text"]; - + } - (void)delete:(id)sender { - + [_mutableAttributedString setAttributedString:self.attributedString]; [_mutableAttributedString deleteCharactersInRange:_selectedRange]; - [self.inputDelegate textWillChange:self]; + [self.inputDelegate textWillChange:self]; [self setAttributedString:_mutableAttributedString]; - [self.inputDelegate textDidChange:self]; - + [self.inputDelegate textDidChange:self]; + self.selectedRange = NSMakeRange(_selectedRange.location, 0); - + } - (void)replace:(id)sender { - - + + } @end @@ -2273,7 +2301,7 @@ - (void)replace:(id)sender { // MARK: EGOIndexedPosition ///////////////////////////////////////////////////////////////////////////// -@implementation EGOIndexedPosition +@implementation EGOIndexedPosition @synthesize index=_index; + (EGOIndexedPosition *)positionWithIndex:(NSUInteger)index { @@ -2290,13 +2318,13 @@ + (EGOIndexedPosition *)positionWithIndex:(NSUInteger)index { // MARK: EGOIndexedRange ///////////////////////////////////////////////////////////////////////////// -@implementation EGOIndexedRange +@implementation EGOIndexedRange @synthesize range=_range; + (EGOIndexedRange *)rangeWithNSRange:(NSRange)theRange { if (theRange.location == NSNotFound) return nil; - + EGOIndexedRange *range = [[EGOIndexedRange alloc] init]; range.range = theRange; return [range autorelease]; @@ -2328,26 +2356,26 @@ @implementation EGOContentView - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { - + self.userInteractionEnabled = NO; self.layer.geometryFlipped = YES; self.backgroundColor = [UIColor whiteColor]; - + } return self; } - (void)layoutSubviews { [super layoutSubviews]; - + [self.delegate textChanged]; // reset layout on frame / orientation change - + } - (void)drawRect:(CGRect)rect { - + [_delegate drawContentInRect:rect]; - + } - (void)dealloc { @@ -2370,31 +2398,31 @@ @implementation EGOCaretView - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { self.backgroundColor = [EGOTextView caretColor]; - } + } return self; } - (void)show { - + [self.layer removeAllAnimations]; - + } - (void)didMoveToSuperview { if (self.superview) { - + [self delayBlink]; - + } else { - + [self.layer removeAllAnimations]; - + } } - (void)delayBlink { - + CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; animation.values = [NSArray arrayWithObjects:[NSNumber numberWithFloat:1.0f], [NSNumber numberWithFloat:1.0f], [NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:0.0f], nil]; animation.calculationMode = kCAAnimationCubic; @@ -2402,7 +2430,7 @@ - (void)delayBlink { animation.beginTime = CACurrentMediaTime() + kInitialBlinkDelay; animation.repeatCount = CGFLOAT_MAX; [self.layer addAnimation:animation forKey:@"BlinkAnimation"]; - + } - (void)dealloc { @@ -2429,24 +2457,24 @@ - (id)init { - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); - + [[UIImage imageNamed:@"loupe-lo.png"] drawInRect:rect]; - + if ((_contentImage!=nil)) { - + CGContextSaveGState(ctx); CGContextClipToMask(ctx, rect, [UIImage imageNamed:@"loupe-mask.png"].CGImage); - [_contentImage drawInRect:rect]; + [_contentImage drawInRect:rect]; CGContextRestoreGState(ctx); - + } - + [[UIImage imageNamed:@"loupe-hi.png"] drawInRect:rect]; - + } - (void)setContentImage:(UIImage *)image { - + [_contentImage release], _contentImage=nil; _contentImage = [image retain]; [self setNeedsDisplay]; @@ -2476,7 +2504,7 @@ @implementation EGOTextWindow static const CGFloat kMagnifyScale = 1.0f; static const NSTimeInterval kDefaultAnimationDuration = 0.15f; -- (id)initWithFrame:(CGRect)frame { +- (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { self.backgroundColor = [UIColor clearColor]; _type = EGOWindowLoupe; @@ -2489,11 +2517,11 @@ - (NSInteger)selectionForRange:(NSRange)range { } - (void)showFromView:(UIView*)view rect:(CGRect)rect { - + CGPoint pos = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)); - + if (!_showing) { - + if (_view==nil) { UIView *view; if (_type==EGOWindowLoupe) { @@ -2505,61 +2533,61 @@ - (void)showFromView:(UIView*)view rect:(CGRect)rect { _view=view; [view release]; } - + CGRect frame = _view.frame; frame.origin.x = floorf(pos.x - (_view.bounds.size.width/2)); frame.origin.y = floorf(pos.y - _view.bounds.size.height); - + if (_type==EGOWindowMagnify) { - + frame.origin.y = MAX(frame.origin.y+8.0f, 0.0f); frame.origin.x += 2.0f; - + } else { - + frame.origin.y = MAX(frame.origin.y-10.0f, -40.0f); - + } - + CGRect originFrame = frame; frame.origin.y += frame.size.height/2; _view.frame = frame; _view.transform = CGAffineTransformMakeScale(0.01f, 0.01f); _view.alpha = 0.01f; - + [UIView animateWithDuration:kDefaultAnimationDuration animations:^{ - + _view.alpha = 1.0f; _view.transform = CGAffineTransformMakeScale(1.0f, 1.0f); _view.frame = originFrame; } completion:^(BOOL finished) { - + _showing=YES; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (0.0f*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self renderWithContentView:view fromRect:rect]; }); - + }]; - + } - + } - (void)hide:(BOOL)animated { - + if ((_view!=nil)) { - + [UIView animateWithDuration:kDefaultAnimationDuration animations:^{ - + CGRect frame = _view.frame; CGPoint center = _view.center; frame.origin.x = floorf(center.x-(frame.size.width/2)); frame.origin.y = center.y; _view.frame = frame; _view.transform = CGAffineTransformMakeScale(0.01f, 0.01f); - + } completion:^(BOOL finished) { _showing=NO; @@ -2569,24 +2597,24 @@ - (void)hide:(BOOL)animated { self.hidden = YES; }]; - + } - + } - (UIImage*)screenshotFromCaretFrame:(CGRect)rect inView:(UIView*)view scale:(BOOL)scale { - + CGRect offsetRect = [self convertRect:rect toView:view]; offsetRect.origin.y += ((UIScrollView*)view.superview).contentOffset.y; offsetRect.origin.y -= _view.bounds.size.height+20.0f; offsetRect.origin.x -= (_view.bounds.size.width/2); - - //CGFloat magnifyScale = 1.0f; - + + //CGFloat magnifyScale = 1.0f; + if (scale) { //CGFloat max = 24.0f; - // magnifyScale = max/offsetRect.size.height; - // NSLog(@"max %f scale %f", max, magnifyScale); + // magnifyScale = max/offsetRect.size.height; + // NSLog(@"max %f scale %f", max, magnifyScale); } else if (rect.size.height < 22.0f) { //magnifyScale = 22.0f/offsetRect.size.height; //NSLog(@"cale %f", magnifyScale); @@ -2601,47 +2629,47 @@ - (UIImage*)screenshotFromCaretFrame:(CGRect)rect inView:(UIView*)view scale:(BO CGContextSaveGState(ctx); CGContextTranslateCTM(ctx, 0, view.bounds.size.height); CGContextScaleCTM(ctx, 1.0, -1.0); - -// CGContextConcatCTM(ctx, CGAffineTransformMakeScale(magnifyScale, magnifyScale)); + + // CGContextConcatCTM(ctx, CGAffineTransformMakeScale(magnifyScale, magnifyScale)); CGContextConcatCTM(ctx, CGAffineTransformMakeTranslation(-(offsetRect.origin.x), offsetRect.origin.y)); - + UIView *selectionView = nil; CGRect selectionFrame = CGRectZero; - + for (UIView *subview in view.subviews){ if ([subview isKindOfClass:[EGOSelectionView class]]) { selectionView = subview; } } - + if (selectionView!=nil) { selectionFrame = selectionView.frame; CGRect newFrame = selectionFrame; newFrame.origin.y = (selectionFrame.size.height - view.bounds.size.height) - ((selectionFrame.origin.y + selectionFrame.size.height) - view.bounds.size.height); selectionView.frame = newFrame; } - + [view.layer renderInContext:ctx]; - + if (selectionView!=nil) { selectionView.frame = selectionFrame; } - - + + CGContextRestoreGState(ctx); UIImage *aImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return aImage; - + } - (void)renderWithContentView:(UIView*)view fromRect:(CGRect)rect { - + CGPoint pos = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)); - + if (_showing && _view!=nil) { - + CGRect frame = _view.frame; frame.origin.x = floorf((pos.x - (_view.bounds.size.width/2)) + (rect.size.width/2)); frame.origin.y = floorf(pos.y - _view.bounds.size.height); @@ -2657,13 +2685,13 @@ - (void)renderWithContentView:(UIView*)view fromRect:(CGRect)rect { UIImage *image = [self screenshotFromCaretFrame:rect inView:view scale:(_type==EGOWindowMagnify)]; [(EGOLoupeView*)_view setContentImage:image]; - + } - + } - (void)updateWindowTransform { - + self.frame = [[UIScreen mainScreen] bounds]; switch ([[UIApplication sharedApplication] statusBarOrientation]) { case UIInterfaceOrientationPortrait: @@ -2681,7 +2709,7 @@ - (void)updateWindowTransform { default: break; } - + } - (void)layoutSubviews { @@ -2712,30 +2740,30 @@ - (id)init { } - (void)drawRect:(CGRect)rect { - + CGContextRef ctx = UIGraphicsGetCurrentContext(); - + [[UIImage imageNamed:@"magnifier-ranged-lo.png"] drawInRect:rect]; - + if ((_contentImage!=nil)) { - + CGContextSaveGState(ctx); CGContextClipToMask(ctx, rect, [UIImage imageNamed:@"magnifier-ranged-mask.png"].CGImage); - [_contentImage drawInRect:rect]; + [_contentImage drawInRect:rect]; CGContextRestoreGState(ctx); - + } - + [[UIImage imageNamed:@"magnifier-ranged-hi.png"] drawInRect:rect]; - + } - (void)setContentImage:(UIImage *)image { - + [_contentImage release], _contentImage=nil; _contentImage = [image retain]; [self setNeedsDisplay]; - + } - (void)dealloc { @@ -2755,32 +2783,32 @@ @implementation EGOSelectionView - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { - - self.backgroundColor = [UIColor clearColor]; + + self.backgroundColor = [UIColor clearColor]; self.userInteractionEnabled = NO; self.layer.geometryFlipped = YES; - + } return self; } - (void)setBeginCaret:(CGRect)begin endCaret:(CGRect)end { - + if(!self.superview) return; - - self.frame = CGRectMake(begin.origin.x, begin.origin.y + begin.size.height, end.origin.x - begin.origin.x, (end.origin.y-end.size.height)-begin.origin.y); + + self.frame = CGRectMake(begin.origin.x, begin.origin.y + begin.size.height, end.origin.x - begin.origin.x, (end.origin.y-end.size.height)-begin.origin.y); begin = [self.superview convertRect:begin toView:self]; end = [self.superview convertRect:end toView:self]; - + if (_leftCaret==nil) { UIView *view = [[UIView alloc] initWithFrame:begin]; view.backgroundColor = [EGOTextView caretColor]; - [self addSubview:view]; + [self addSubview:view]; _leftCaret=[view retain]; [view release]; } - + if (_leftDot==nil) { UIImage *dotImage = [UIImage imageNamed:@"drag-dot.png"]; UIImageView *view = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, dotImage.size.width, dotImage.size.height)]; @@ -2789,11 +2817,11 @@ - (void)setBeginCaret:(CGRect)begin endCaret:(CGRect)end { _leftDot = view; [view release]; } - + CGFloat _dotShadowOffset = 5.0f; _leftCaret.frame = begin; _leftDot.frame = CGRectMake(floorf(_leftCaret.center.x - (_leftDot.bounds.size.width/2)), _leftCaret.frame.origin.y-(_leftDot.bounds.size.height-_dotShadowOffset), _leftDot.bounds.size.width, _leftDot.bounds.size.height); - + if (_rightCaret==nil) { UIView *view = [[UIView alloc] initWithFrame:end]; view.backgroundColor = [EGOTextView caretColor]; @@ -2801,7 +2829,7 @@ - (void)setBeginCaret:(CGRect)begin endCaret:(CGRect)end { _rightCaret = [view retain]; [view release]; } - + if (_rightDot==nil) { UIImage *dotImage = [UIImage imageNamed:@"drag-dot.png"]; UIImageView *view = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, dotImage.size.width, dotImage.size.height)]; @@ -2810,17 +2838,17 @@ - (void)setBeginCaret:(CGRect)begin endCaret:(CGRect)end { _rightDot = view; [view release]; } - + _rightCaret.frame = end; _rightDot.frame = CGRectMake(floorf(_rightCaret.center.x - (_rightDot.bounds.size.width/2)), CGRectGetMaxY(_rightCaret.frame), _rightDot.bounds.size.width, _rightDot.bounds.size.height); - + } - (void)dealloc { - - [_leftCaret release], _leftCaret=nil; - [_rightCaret release], _rightCaret=nil; + + [_leftCaret release], _leftCaret=nil; + [_rightCaret release], _rightCaret=nil; _rightDot=nil; _leftDot=nil; [super dealloc]; diff --git a/EGOTextView_Demo.xcodeproj/project.pbxproj b/EGOTextView_Demo.xcodeproj/project.pbxproj index 9aac921..0fe3b95 100644 --- a/EGOTextView_Demo.xcodeproj/project.pbxproj +++ b/EGOTextView_Demo.xcodeproj/project.pbxproj @@ -190,6 +190,8 @@ /* Begin PBXProject section */ 03FF5EBE135CA49D00268656 /* Project object */ = { isa = PBXProject; + attributes = { + }; buildConfigurationList = 03FF5EC1135CA49D00268656 /* Build configuration list for PBXProject "EGOTextView_Demo" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; @@ -319,6 +321,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "EGOTextView_Demo/EGOTextView_Demo-Prefix.pch"; + GCC_VERSION = ""; INFOPLIST_FILE = "EGOTextView_Demo/EGOTextView_Demo-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -334,6 +337,7 @@ COPY_PHASE_STRIP = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "EGOTextView_Demo/EGOTextView_Demo-Prefix.pch"; + GCC_VERSION = ""; INFOPLIST_FILE = "EGOTextView_Demo/EGOTextView_Demo-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; PRODUCT_NAME = "$(TARGET_NAME)";