From bcd446c4f559e56f254a703c5e6c6efabb94f461 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 15 Sep 2013 14:54:16 -0700 Subject: [PATCH 01/42] - Initial commit for bullet list TODO: - Make bullets bold - Calculate width of bullet-string according to the font and set headIndent and firstLineHeadIndent according to this width --- .../Categories/NSString+RichTextEditor.h | 15 ++++++++ .../Categories/NSString+RichTextEditor.m | 21 +++++++++++ RichTextEditor/Source/RichTextEditor.m | 35 +++++++++++++++++-- RichTextEditor/Source/RichTextEditorToolbar.h | 5 +-- RichTextEditor/Source/RichTextEditorToolbar.m | 19 ++++++---- 5 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 RichTextEditor/Source/Categories/NSString+RichTextEditor.h create mode 100644 RichTextEditor/Source/Categories/NSString+RichTextEditor.m diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h new file mode 100644 index 0000000..d48c4be --- /dev/null +++ b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h @@ -0,0 +1,15 @@ +// +// NSString+RichTextEditor.h +// RichTextEditor +// +// Created by Aryan Gh on 9/15/13. +// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. +// + +#import + +@interface NSString (RichTextEditor) + +- (BOOL)startsWithString:(NSString *)string; + +@end diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m new file mode 100644 index 0000000..e48be01 --- /dev/null +++ b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m @@ -0,0 +1,21 @@ +// +// NSString+RichTextEditor.m +// RichTextEditor +// +// Created by Aryan Gh on 9/15/13. +// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. +// + +#import "NSString+RichTextEditor.h" + +@implementation NSString (RichTextEditor) + +- (BOOL)startsWithString:(NSString *)string +{ + if (!string.length || string.length > self.length) + return NO; + + return ([[self substringToIndex:string.length] isEqualToString:string]) ? YES : NO; +} + +@end diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 86231e9..4f0272d 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -29,9 +29,11 @@ #import #import "UIFont+RichTextEditor.h" #import "NSAttributedString+RichTextEditor.h" +#import "NSString+RichTextEditor.h" #import "UIView+RichTextEditor.h" #define RICHTEXTEDITOR_TOOLBAR_HEIGHT 40 +#define BULLET_STRING @"•\t" @interface RichTextEditor() @property (nonatomic, strong) RichTextEditorToolbar *toolBar; @@ -317,9 +319,38 @@ - (void)richTextEditorToolbarDidSelectTextAlignment:(NSTextAlignment)textAlignme }]; } -- (void)richTextEditorToolbarDidSelectBulletPoint +- (void)richTextEditorToolbarDidSelectBulletList { - // TODO: implement this + NSRange range = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; + NSMutableAttributedString *currentAttributedString = [self.attributedText mutableCopy]; + NSDictionary *dictionary = [self dictionaryAtIndex:range.location]; + NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; + + if (!paragraphStyle) + paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + + if ([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING]) + { + range = NSMakeRange(range.location, range.length-2); + + [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, 2)]; + + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = 0; + } + else + { + range = NSMakeRange(range.location, range.length+2); + + NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; + [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; + + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = 27; + } + + self.attributedText = currentAttributedString; + [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:range]; } #pragma mark - Private Methods - diff --git a/RichTextEditor/Source/RichTextEditorToolbar.h b/RichTextEditor/Source/RichTextEditorToolbar.h index ff6be4d..7ecc845 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.h +++ b/RichTextEditor/Source/RichTextEditorToolbar.h @@ -53,7 +53,8 @@ typedef enum{ RichTextEditorFeatureTextForegroundColor = 1 << 11, RichTextEditorFeatureParagraphIndentation = 1 << 12, RichTextEditorFeatureParagraphFirstLineIndentation = 1 << 13, - RichTextEditorFeatureAll = 1 << 14 + RichTextEditorFeatureBulletList = 1 << 14, + RichTextEditorFeatureAll = 1 << 15 }RichTextEditorFeature; @protocol RichTextEditorToolbarDelegate @@ -61,7 +62,7 @@ typedef enum{ - (void)richTextEditorToolbarDidSelectItalic; - (void)richTextEditorToolbarDidSelectUnderline; - (void)richTextEditorToolbarDidSelectStrikeThrough; -- (void)richTextEditorToolbarDidSelectBulletPoint; +- (void)richTextEditorToolbarDidSelectBulletList; - (void)richTextEditorToolbarDidSelectParagraphFirstLineHeadIndent; - (void)richTextEditorToolbarDidSelectParagraphIndentation:(ParagraphIndentation)paragraphIndentation; - (void)richTextEditorToolbarDidSelectFontSize:(NSNumber *)fontSize; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 4e14ec8..a49ef43 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -56,7 +56,7 @@ @interface RichTextEditorToolbar() Date: Sun, 15 Sep 2013 20:34:19 -0700 Subject: [PATCH 02/42] - Calculating the width of the bullet string according to the font and setting it as paragraph indentation to keep all lines in bullet list aligned --- RichTextEditor/Source/RichTextEditor.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 4f0272d..7fc666c 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -344,9 +344,12 @@ - (void)richTextEditorToolbarDidSelectBulletList NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; + CGSize expectedStringSize = [BULLET_STRING sizeWithFont:[dictionary objectForKey:NSFontAttributeName] + constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT) + lineBreakMode:NSLineBreakByWordWrapping]; paragraphStyle.firstLineHeadIndent = 0; - paragraphStyle.headIndent = 27; + paragraphStyle.headIndent = expectedStringSize.width; } self.attributedText = currentAttributedString; From 8641ed2871892594f3c6d6240adc2fb54a4a365c Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Fri, 20 Sep 2013 00:55:28 -0700 Subject: [PATCH 03/42] - Added ability to create bullet list by selecting multiple paragraphs at a time --- RichTextEditor/Source/RichTextEditor.m | 68 +++++++++++++++----------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 7fc666c..6b95ba5 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -321,39 +321,51 @@ - (void)richTextEditorToolbarDidSelectTextAlignment:(NSTextAlignment)textAlignme - (void)richTextEditorToolbarDidSelectBulletList { - NSRange range = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; - NSMutableAttributedString *currentAttributedString = [self.attributedText mutableCopy]; - NSDictionary *dictionary = [self dictionaryAtIndex:range.location]; - NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; - - if (!paragraphStyle) - paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; + __block NSInteger rangeOffset = 0; - if ([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING]) - { - range = NSMakeRange(range.location, range.length-2); - - [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, 2)]; + [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ + NSRange range = NSMakeRange(paragraphRange.location + rangeOffset, paragraphRange.length); + NSMutableAttributedString *currentAttributedString = [self.attributedText mutableCopy]; + NSDictionary *dictionary = [self dictionaryAtIndex:range.location]; + NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; - paragraphStyle.firstLineHeadIndent = 0; - paragraphStyle.headIndent = 0; - } - else - { - range = NSMakeRange(range.location, range.length+2); + if (!paragraphStyle) + paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; - [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; - CGSize expectedStringSize = [BULLET_STRING sizeWithFont:[dictionary objectForKey:NSFontAttributeName] - constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT) - lineBreakMode:NSLineBreakByWordWrapping]; + if (([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING])) + { + range = NSMakeRange(range.location, range.length-2); + + [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, 2)]; + + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = 0; + + rangeOffset = rangeOffset - 2; + } + else + { + range = NSMakeRange(range.location, range.length+2); + + NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; + [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; + CGSize expectedStringSize = [BULLET_STRING sizeWithFont:[dictionary objectForKey:NSFontAttributeName] + constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT) + lineBreakMode:NSLineBreakByWordWrapping]; + + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = expectedStringSize.width; + + rangeOffset = rangeOffset + 2; + } - paragraphStyle.firstLineHeadIndent = 0; - paragraphStyle.headIndent = expectedStringSize.width; - } + self.attributedText = currentAttributedString; + [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:range]; + }]; - self.attributedText = currentAttributedString; - [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:range]; + NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; + [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; } #pragma mark - Private Methods - From bb062cae3260cd45db969e90ef3128892d3678fb Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 14:25:28 -0700 Subject: [PATCH 04/42] - Detect whether bullet should be inserted or not based on the state of the first paragraph - Making the inserted bullet bold - Fixed a memory leak --- .../Categories/NSString+RichTextEditor.h | 19 ++++++++++ .../Categories/NSString+RichTextEditor.m | 19 ++++++++++ .../Source/Categories/UIFont+RichTextEditor.m | 1 + RichTextEditor/Source/RichTextEditor.m | 36 ++++++++++++++++--- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h index d48c4be..2667f4c 100644 --- a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h +++ b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h @@ -5,6 +5,25 @@ // Created by Aryan Gh on 9/15/13. // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. // +// https://github.com/aryaxt/iOS-Rich-Text-Editor +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. #import diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m index e48be01..2aa80c2 100644 --- a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m @@ -5,6 +5,25 @@ // Created by Aryan Gh on 9/15/13. // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. // +// https://github.com/aryaxt/iOS-Rich-Text-Editor +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. #import "NSString+RichTextEditor.h" diff --git a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m index 10b95ce..fd21865 100644 --- a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m @@ -61,6 +61,7 @@ + (UIFont *)fontWithName:(NSString *)name size:(CGFloat)size boldTrait:(BOOL)isB if (newFontRef) { NSString *fontNameKey = (__bridge NSString *)(CTFontCopyName(newFontRef, kCTFontPostScriptNameKey)); + CFRelease(newFontRef); return [UIFont fontWithName:fontNameKey size:CTFontGetSize(newFontRef)]; } diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 6b95ba5..12f6210 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -322,6 +322,9 @@ - (void)richTextEditorToolbarDidSelectTextAlignment:(NSTextAlignment)textAlignme - (void)richTextEditorToolbarDidSelectBulletList { NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; + NSRange rangeOfFirstParagraphRange = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; + BOOL firstParagraphHasBullet = ([[[self.attributedText string] substringFromIndex:rangeOfFirstParagraphRange.location] startsWithString:BULLET_STRING]) ? YES: NO; + __block NSInteger rangeOffset = 0; [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ @@ -333,7 +336,12 @@ - (void)richTextEditorToolbarDidSelectBulletList if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - if (([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING])) + BOOL currentParagraphHasBullet = ([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING]) ? YES : NO; + + if (firstParagraphHasBullet != currentParagraphHasBullet) + return; + + if (currentParagraphHasBullet) { range = NSMakeRange(range.location, range.length-2); @@ -348,8 +356,20 @@ - (void)richTextEditorToolbarDidSelectBulletList { range = NSMakeRange(range.location, range.length+2); - NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; + // The bullet should be bold + NSMutableDictionary *bulletDictionary = [dictionary mutableCopy]; + UIFont *font = [bulletDictionary objectForKey:NSFontAttributeName]; + UIFont *boldFont = [font fontWithBoldTrait:YES andItalicTrait:NO]; + [bulletDictionary setObject:boldFont forKey:NSFontAttributeName]; + NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:nil]; + [bulletAttributedString setAttributes:bulletDictionary range:NSMakeRange(0, 1)]; + + // The tab after bullet should be similar to existing attributes + [bulletDictionary setObject:font forKey:NSFontAttributeName]; + [bulletAttributedString setAttributes:bulletDictionary range:NSMakeRange(1, 1)]; + [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; + CGSize expectedStringSize = [BULLET_STRING sizeWithFont:[dictionary objectForKey:NSFontAttributeName] constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT) lineBreakMode:NSLineBreakByWordWrapping]; @@ -364,8 +384,16 @@ - (void)richTextEditorToolbarDidSelectBulletList [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:range]; }]; - NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; - [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; + // If paragraph is empty move cursor to front of bullet, so the user can start typing right away + if (rangeOfParagraphsInSelectedText.count == 1 && rangeOfFirstParagraphRange.length == 0) + { + [self setSelectedRange:NSMakeRange(rangeOfFirstParagraphRange.location+2, 0)]; + } + else + { + NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; + [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; + } } #pragma mark - Private Methods - From 35c7f5a2401aa680e8d6fed40dbfcec93baee05e Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 14:27:04 -0700 Subject: [PATCH 05/42] - Fixed comment for bullet list --- RichTextEditor/Source/RichTextEditorToolbar.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index a49ef43..1e89b83 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -381,7 +381,7 @@ - (void)populateToolbar lastAddedView = separatorView; } - // Text color + // Bullet List if (features & RichTextEditorFeatureBulletList || features & RichTextEditorFeatureAll) { [self addView:self.btnBulletList afterView:lastAddedView withSpacing:YES]; From d431626c74e4044eaaefd82d8a0fa6dd35a0d7a2 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 14:36:09 -0700 Subject: [PATCH 06/42] - If initial selected range length is 0, after applying bullet place cursor at initial location. This way the user can apply a bullet on existing paragraph, and continue typing after the bullet is inserted --- RichTextEditor/Source/RichTextEditor.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 12f6210..90b3d8f 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -321,6 +321,7 @@ - (void)richTextEditorToolbarDidSelectTextAlignment:(NSTextAlignment)textAlignme - (void)richTextEditorToolbarDidSelectBulletList { + NSRange initialSelectedRange = self.selectedRange; NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; NSRange rangeOfFirstParagraphRange = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; BOOL firstParagraphHasBullet = ([[[self.attributedText string] substringFromIndex:rangeOfFirstParagraphRange.location] startsWithString:BULLET_STRING]) ? YES: NO; @@ -391,8 +392,15 @@ - (void)richTextEditorToolbarDidSelectBulletList } else { - NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; - [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; + if (initialSelectedRange.length == 0) + { + [self setSelectedRange:NSMakeRange(initialSelectedRange.location+rangeOffset, 0)]; + } + else + { + NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; + [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; + } } } From 1c61b47afa04fbf1871849fbc0c874f4d714286f Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 15:46:13 -0700 Subject: [PATCH 07/42] - Resize toolbar and font button to display full font name - Keep scroll position of toolbar after redrawing content --- RichTextEditor/Source/RichTextEditorToolbar.m | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 1e89b83..5337e1d 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -132,6 +132,8 @@ - (void)updateStateWithAttributes:(NSDictionary *)attributes NSNumber *existingStrikeThrough = [attributes objectForKey:NSStrikethroughStyleAttributeName]; self.btnStrikeThrough.on = (!existingStrikeThrough || existingStrikeThrough.intValue == NSUnderlineStyleNone) ? NO :YES; + + [self populateToolbar]; } #pragma mark - IBActions - @@ -231,6 +233,10 @@ - (void)textAlignmentSelected:(UIButton *)sender - (void)populateToolbar { + CGRect visibleRect; + visibleRect.origin = self.contentOffset; + visibleRect.size = self.bounds.size; + // Remove any existing subviews. for (UIView *subView in self.subviews) { @@ -250,6 +256,11 @@ - (void)populateToolbar if (features & RichTextEditorFeatureFont || features & RichTextEditorFeatureAll) { UIView *separatorView = [self separatorView]; + CGSize size = [self.btnFont sizeThatFits:CGSizeZero]; + CGRect rect = self.btnFont.frame; + rect.size.width = MAX(size.width + 25, 120); + self.btnFont.frame = rect; + [self addView:self.btnFont afterView:lastAddedView withSpacing:YES]; [self addView:separatorView afterView:self.btnFont withSpacing:YES]; lastAddedView = separatorView; @@ -387,6 +398,8 @@ - (void)populateToolbar [self addView:self.btnBulletList afterView:lastAddedView withSpacing:YES]; lastAddedView = self.btnBulletList; } + + [self scrollRectToVisible:visibleRect animated:NO]; } - (void)initializeButtons From c13260b0d2246488f89258b873a4e47cfb902728 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 21:22:12 -0700 Subject: [PATCH 08/42] - Fixed crash when bullet is selected on empty text --- RichTextEditor.xcodeproj/project.pbxproj | 6 ++++++ .../Source/Categories/NSAttributedString+RichTextEditor.m | 3 +++ RichTextEditor/Source/RichTextEditor.m | 3 --- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/RichTextEditor.xcodeproj/project.pbxproj b/RichTextEditor.xcodeproj/project.pbxproj index 236b1e9..cfb63a2 100644 --- a/RichTextEditor.xcodeproj/project.pbxproj +++ b/RichTextEditor.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 15B6E20F17A4C2820015867C /* RichTextEditorToggleButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */; }; 15BFE3E917E4CFE700D89153 /* firstLineIndent.png in Resources */ = {isa = PBXBuildFile; fileRef = 15BFE3E817E4CFE700D89153 /* firstLineIndent.png */; }; 15BFE3EB17E4D01100D89153 /* firstLineIndent@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 15BFE3EA17E4D01100D89153 /* firstLineIndent@2x.png */; }; + 15BFE3F117EFF8B600D89153 /* NSString+RichTextEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */; }; 15C4131317A4444B0073D047 /* backcolor.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F217A4444B0073D047 /* backcolor.png */; }; 15C4131417A4444B0073D047 /* bold.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F317A4444B0073D047 /* bold.png */; }; 15C4131517A4444B0073D047 /* bullist.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F417A4444B0073D047 /* bullist.png */; }; @@ -188,6 +189,8 @@ 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RichTextEditorToggleButton.m; sourceTree = ""; }; 15BFE3E817E4CFE700D89153 /* firstLineIndent.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = firstLineIndent.png; sourceTree = ""; }; 15BFE3EA17E4D01100D89153 /* firstLineIndent@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "firstLineIndent@2x.png"; sourceTree = ""; }; + 15BFE3EF17EFF8B600D89153 /* NSString+RichTextEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RichTextEditor.h"; sourceTree = ""; }; + 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RichTextEditor.m"; sourceTree = ""; }; 15C412F217A4444B0073D047 /* backcolor.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = backcolor.png; sourceTree = ""; }; 15C412F317A4444B0073D047 /* bold.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bold.png; sourceTree = ""; }; 15C412F417A4444B0073D047 /* bullist.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bullist.png; sourceTree = ""; }; @@ -330,6 +333,8 @@ children = ( 153F955B179F9953005B2487 /* NSAttributedString+RichTextEditor.h */, 153F955C179F9953005B2487 /* NSAttributedString+RichTextEditor.m */, + 15BFE3EF17EFF8B600D89153 /* NSString+RichTextEditor.h */, + 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */, 153F955D179F9953005B2487 /* UIFont+RichTextEditor.h */, 153F955E179F9953005B2487 /* UIFont+RichTextEditor.m */, 153F955F179F9953005B2487 /* UIView+RichTextEditor.h */, @@ -647,6 +652,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 15BFE3F117EFF8B600D89153 /* NSString+RichTextEditor.m in Sources */, 153F959D179F9953005B2487 /* WEPopoverContainerView.m in Sources */, 153F95A4179F9953005B2487 /* RichTextEditorToolbar.m in Sources */, 153F959C179F9953005B2487 /* UIBarButtonItem+WEPopover.m in Sources */, diff --git a/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m index 15052e2..5868899 100644 --- a/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m @@ -33,6 +33,9 @@ @implementation NSAttributedString (RichTextEditor) - (NSRange)firstParagraphRangeFromTextRange:(NSRange)range { + if (self.string.length == 0) + return NSMakeRange(0, 0); + NSInteger start = -1; NSInteger end = -1; NSInteger length = 0; diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 90b3d8f..2e5d242 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -424,9 +424,6 @@ - (CGRect)frameOfTextAtRange:(NSRange)range - (void)enumarateThroughParagraphsInRange:(NSRange)range withBlock:(void (^)(NSRange paragraphRange))block { - if (![self hasText]) - return; - NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; for (int i=0 ; i Date: Wed, 25 Sep 2013 18:00:21 -0700 Subject: [PATCH 09/42] - Removed the logic that prevents existing typing attributes to be populated on toolbar during initial load - Added logic to insert a bullet when entering a new line - Added a logic to delete bullet (both characters) when back space is selected in front of a bullet --- RichTextEditor/Source/RichTextEditor.m | 45 ++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 2e5d242..18f731a 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -91,9 +91,16 @@ - (void)commonInitialization [self setupMenuItems]; - //If there is text already, then we do want to update the toolbar. Otherwise we don't. - if ([self hasText]) - [self updateToolbarState]; + [self updateToolbarState]; + + // When text changes check to see if we need to add bullet, or delete bullet on backspace + [[NSNotificationCenter defaultCenter] addObserverForName:UITextViewTextDidChangeNotification + object:self + queue:nil + usingBlock:^(NSNotification *n){ + [self applyBulletListIfApplicable]; + [self deleteBulletListWhenApplicable]; + }]; } #pragma mark - Override Methods - @@ -165,9 +172,6 @@ - (void)setupMenuItems - (void)selectParagraph:(id)sender { - if (![self hasText]) - return; - NSRange range = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; [self setSelectedRange:range]; @@ -619,6 +623,35 @@ - (CGRect)currentScreenBoundsDependOnOrientation return screenBounds ; } +- (void)applyBulletListIfApplicable +{ + NSRange rangeOfCurrentParagraph = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; + if (rangeOfCurrentParagraph.length != 0) + return; + + NSRange rangeOfPreviousParagraph = [self.attributedText firstParagraphRangeFromTextRange:NSMakeRange(rangeOfCurrentParagraph.location-1, 0)]; + if ([[self.attributedText.string substringFromIndex:rangeOfPreviousParagraph.location] startsWithString:BULLET_STRING]) + [self richTextEditorToolbarDidSelectBulletList]; +} + +- (void)deleteBulletListWhenApplicable +{ + NSRange range = self.selectedRange; + + if (range.location > 0) + { + NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; + + if ([[[attributedString.string substringFromIndex:range.location-1] substringToIndex:1] isEqual:@"•"]) + { + [attributedString deleteCharactersInRange:NSMakeRange(range.location-1, 1)]; + self.attributedText = attributedString; + [self setSelectedRange:NSMakeRange(range.location-1, 0)]; + } + + } +} + #pragma mark - RichTextEditorToolbarDataSource Methods - - (NSArray *)fontFamilySelectionForRichTextEditorToolbar From 7cd8d1d103f9832e96acf191107f85babe4211a7 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Wed, 25 Sep 2013 18:23:46 -0700 Subject: [PATCH 10/42] - Fixed a memory leak --- RichTextEditor/Source/Categories/UIFont+RichTextEditor.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m index fd21865..7b034ad 100644 --- a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m @@ -58,6 +58,9 @@ + (UIFont *)fontWithName:(NSString *)name size:(CGFloat)size boldTrait:(BOOL)isB newFontRef = CTFontCreateCopyWithSymbolicTraits(fontWithoutTrait, 0.0, NULL, traits, traits); } + if (fontWithoutTrait) + CFRelease(fontWithoutTrait); + if (newFontRef) { NSString *fontNameKey = (__bridge NSString *)(CTFontCopyName(newFontRef, kCTFontPostScriptNameKey)); From c19b17d6350829147713a0bdb8c2736af218ab0b Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Wed, 25 Sep 2013 18:35:20 -0700 Subject: [PATCH 11/42] - Performance improvement (Don't make a copy of attributed string unless there is a bullet and the mutable copy is needed) --- RichTextEditor/Source/RichTextEditor.m | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 18f731a..8f7b5dd 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -640,15 +640,13 @@ - (void)deleteBulletListWhenApplicable if (range.location > 0) { - NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; - - if ([[[attributedString.string substringFromIndex:range.location-1] substringToIndex:1] isEqual:@"•"]) + if ([[[self.attributedText.string substringFromIndex:range.location-1] substringToIndex:1] isEqual:@"•"]) { - [attributedString deleteCharactersInRange:NSMakeRange(range.location-1, 1)]; - self.attributedText = attributedString; + NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; + [mutableAttributedString deleteCharactersInRange:NSMakeRange(range.location-1, 1)]; + self.attributedText = mutableAttributedString; [self setSelectedRange:NSMakeRange(range.location-1, 0)]; } - } } From 164c203550b2538a3f86594a42106fa5af5f6e4e Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 29 Sep 2013 19:45:45 -0700 Subject: [PATCH 12/42] - Workaround for the bug where in iOS6 accessing typingAttribute on an empty text causes a crash - Added a method to pass html string and populate the rich text editor (Only works on iOS7 +) - Making bullet list consistent with the way iOS parses html to bullet attributed string --- RichTextEditor.xcodeproj/project.pbxproj | 6 -- .../Categories/NSString+RichTextEditor.h | 34 ------- .../Categories/NSString+RichTextEditor.m | 40 --------- RichTextEditor/Source/RichTextEditor.h | 1 + RichTextEditor/Source/RichTextEditor.m | 90 ++++++++++++------- 5 files changed, 60 insertions(+), 111 deletions(-) delete mode 100644 RichTextEditor/Source/Categories/NSString+RichTextEditor.h delete mode 100644 RichTextEditor/Source/Categories/NSString+RichTextEditor.m diff --git a/RichTextEditor.xcodeproj/project.pbxproj b/RichTextEditor.xcodeproj/project.pbxproj index cfb63a2..236b1e9 100644 --- a/RichTextEditor.xcodeproj/project.pbxproj +++ b/RichTextEditor.xcodeproj/project.pbxproj @@ -60,7 +60,6 @@ 15B6E20F17A4C2820015867C /* RichTextEditorToggleButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */; }; 15BFE3E917E4CFE700D89153 /* firstLineIndent.png in Resources */ = {isa = PBXBuildFile; fileRef = 15BFE3E817E4CFE700D89153 /* firstLineIndent.png */; }; 15BFE3EB17E4D01100D89153 /* firstLineIndent@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 15BFE3EA17E4D01100D89153 /* firstLineIndent@2x.png */; }; - 15BFE3F117EFF8B600D89153 /* NSString+RichTextEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */; }; 15C4131317A4444B0073D047 /* backcolor.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F217A4444B0073D047 /* backcolor.png */; }; 15C4131417A4444B0073D047 /* bold.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F317A4444B0073D047 /* bold.png */; }; 15C4131517A4444B0073D047 /* bullist.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F417A4444B0073D047 /* bullist.png */; }; @@ -189,8 +188,6 @@ 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RichTextEditorToggleButton.m; sourceTree = ""; }; 15BFE3E817E4CFE700D89153 /* firstLineIndent.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = firstLineIndent.png; sourceTree = ""; }; 15BFE3EA17E4D01100D89153 /* firstLineIndent@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "firstLineIndent@2x.png"; sourceTree = ""; }; - 15BFE3EF17EFF8B600D89153 /* NSString+RichTextEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RichTextEditor.h"; sourceTree = ""; }; - 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RichTextEditor.m"; sourceTree = ""; }; 15C412F217A4444B0073D047 /* backcolor.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = backcolor.png; sourceTree = ""; }; 15C412F317A4444B0073D047 /* bold.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bold.png; sourceTree = ""; }; 15C412F417A4444B0073D047 /* bullist.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bullist.png; sourceTree = ""; }; @@ -333,8 +330,6 @@ children = ( 153F955B179F9953005B2487 /* NSAttributedString+RichTextEditor.h */, 153F955C179F9953005B2487 /* NSAttributedString+RichTextEditor.m */, - 15BFE3EF17EFF8B600D89153 /* NSString+RichTextEditor.h */, - 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */, 153F955D179F9953005B2487 /* UIFont+RichTextEditor.h */, 153F955E179F9953005B2487 /* UIFont+RichTextEditor.m */, 153F955F179F9953005B2487 /* UIView+RichTextEditor.h */, @@ -652,7 +647,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 15BFE3F117EFF8B600D89153 /* NSString+RichTextEditor.m in Sources */, 153F959D179F9953005B2487 /* WEPopoverContainerView.m in Sources */, 153F95A4179F9953005B2487 /* RichTextEditorToolbar.m in Sources */, 153F959C179F9953005B2487 /* UIBarButtonItem+WEPopover.m in Sources */, diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h deleted file mode 100644 index 2667f4c..0000000 --- a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// NSString+RichTextEditor.h -// RichTextEditor -// -// Created by Aryan Gh on 9/15/13. -// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. -// -// https://github.com/aryaxt/iOS-Rich-Text-Editor -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import - -@interface NSString (RichTextEditor) - -- (BOOL)startsWithString:(NSString *)string; - -@end diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m deleted file mode 100644 index 2aa80c2..0000000 --- a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// NSString+RichTextEditor.m -// RichTextEditor -// -// Created by Aryan Gh on 9/15/13. -// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. -// -// https://github.com/aryaxt/iOS-Rich-Text-Editor -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import "NSString+RichTextEditor.h" - -@implementation NSString (RichTextEditor) - -- (BOOL)startsWithString:(NSString *)string -{ - if (!string.length || string.length > self.length) - return NO; - - return ([[self substringToIndex:string.length] isEqualToString:string]) ? YES : NO; -} - -@end diff --git a/RichTextEditor/Source/RichTextEditor.h b/RichTextEditor/Source/RichTextEditor.h index 80e1081..b23f4d3 100644 --- a/RichTextEditor/Source/RichTextEditor.h +++ b/RichTextEditor/Source/RichTextEditor.h @@ -49,5 +49,6 @@ - (void)setBorderColor:(UIColor*)borderColor; - (void)setBorderWidth:(CGFloat)borderWidth; - (NSString *)htmlString; +- (void)setHtmlString:(NSString *)htmlString; @end diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 8f7b5dd..2f0ea34 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -29,11 +29,11 @@ #import #import "UIFont+RichTextEditor.h" #import "NSAttributedString+RichTextEditor.h" -#import "NSString+RichTextEditor.h" #import "UIView+RichTextEditor.h" +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) #define RICHTEXTEDITOR_TOOLBAR_HEIGHT 40 -#define BULLET_STRING @"•\t" +#define BULLET_STRING @"\t•\t" @interface RichTextEditor() @property (nonatomic, strong) RichTextEditorToolbar *toolBar; @@ -90,7 +90,6 @@ - (void)commonInitialization self.defaultIndentationSize = 15; [self setupMenuItems]; - [self updateToolbarState]; // When text changes check to see if we need to add bullet, or delete bullet on backspace @@ -181,9 +180,42 @@ - (void)selectParagraph:(id)sender #pragma mark - Public Methods - +- (void)setHtmlString:(NSString *)htmlString +{ + if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) + { + NSLog(@"Method setHtmlString is only supported on iOS 7 and above"); + return; + } + + NSError *error ; + NSData *data = [htmlString dataUsingEncoding:NSUTF8StringEncoding]; + NSAttributedString *str = [[NSAttributedString alloc] initWithData:data + + options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, + NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} + documentAttributes:nil error:&error]; + + if (error) + NSLog(@"%@", error); + else + self.attributedText = str; +} + - (NSString *)htmlString { - return [self.attributedText htmlString]; + if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) + { + NSLog(@"Method setHtmlString is only supported on iOS 7 and above"); + return nil; + } + + NSData *data = [self.attributedText dataFromRange:NSMakeRange(0, self.text.length) documentAttributes:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, + NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} + error:nil]; + + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + //return [self.attributedText htmlString]; } - (void)setBorderColor:(UIColor *)borderColor @@ -328,50 +360,42 @@ - (void)richTextEditorToolbarDidSelectBulletList NSRange initialSelectedRange = self.selectedRange; NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; NSRange rangeOfFirstParagraphRange = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; - BOOL firstParagraphHasBullet = ([[[self.attributedText string] substringFromIndex:rangeOfFirstParagraphRange.location] startsWithString:BULLET_STRING]) ? YES: NO; + BOOL firstParagraphHasBullet = ([[[self.attributedText string] substringFromIndex:rangeOfFirstParagraphRange.location] hasPrefix:BULLET_STRING]) ? YES: NO; __block NSInteger rangeOffset = 0; [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ NSRange range = NSMakeRange(paragraphRange.location + rangeOffset, paragraphRange.length); NSMutableAttributedString *currentAttributedString = [self.attributedText mutableCopy]; - NSDictionary *dictionary = [self dictionaryAtIndex:range.location]; + NSDictionary *dictionary = [self dictionaryAtIndex:MAX((int)range.location-1, 0)]; NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - BOOL currentParagraphHasBullet = ([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING]) ? YES : NO; + BOOL currentParagraphHasBullet = ([[[currentAttributedString string] substringFromIndex:range.location] hasPrefix:BULLET_STRING]) ? YES : NO; if (firstParagraphHasBullet != currentParagraphHasBullet) return; if (currentParagraphHasBullet) { - range = NSMakeRange(range.location, range.length-2); + range = NSMakeRange(range.location, range.length - BULLET_STRING.length); - [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, 2)]; + [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, BULLET_STRING.length)]; paragraphStyle.firstLineHeadIndent = 0; paragraphStyle.headIndent = 0; - rangeOffset = rangeOffset - 2; + rangeOffset = rangeOffset - BULLET_STRING.length; } else { - range = NSMakeRange(range.location, range.length+2); + range = NSMakeRange(range.location, range.length + BULLET_STRING.length); // The bullet should be bold - NSMutableDictionary *bulletDictionary = [dictionary mutableCopy]; - UIFont *font = [bulletDictionary objectForKey:NSFontAttributeName]; - UIFont *boldFont = [font fontWithBoldTrait:YES andItalicTrait:NO]; - [bulletDictionary setObject:boldFont forKey:NSFontAttributeName]; NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:nil]; - [bulletAttributedString setAttributes:bulletDictionary range:NSMakeRange(0, 1)]; - - // The tab after bullet should be similar to existing attributes - [bulletDictionary setObject:font forKey:NSFontAttributeName]; - [bulletAttributedString setAttributes:bulletDictionary range:NSMakeRange(1, 1)]; + [bulletAttributedString setAttributes:dictionary range:NSMakeRange(0, BULLET_STRING.length)]; [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; @@ -382,7 +406,7 @@ - (void)richTextEditorToolbarDidSelectBulletList paragraphStyle.firstLineHeadIndent = 0; paragraphStyle.headIndent = expectedStringSize.width; - rangeOffset = rangeOffset + 2; + rangeOffset = rangeOffset + BULLET_STRING.length; } self.attributedText = currentAttributedString; @@ -392,7 +416,7 @@ - (void)richTextEditorToolbarDidSelectBulletList // If paragraph is empty move cursor to front of bullet, so the user can start typing right away if (rangeOfParagraphsInSelectedText.count == 1 && rangeOfFirstParagraphRange.length == 0) { - [self setSelectedRange:NSMakeRange(rangeOfFirstParagraphRange.location+2, 0)]; + [self setSelectedRange:NSMakeRange(rangeOfFirstParagraphRange.location + BULLET_STRING.length, 0)]; } else { @@ -443,6 +467,10 @@ - (void)enumarateThroughParagraphsInRange:(NSRange)range withBlock:(void (^)(NSR - (void)updateToolbarState { + // There is a bug in iOS6 that causes a crash when accessing typingAttribute on an empty text + if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0") && ![self hasText]) + return; + // If no text exists or typing attributes is in progress update toolbar using typing attributes instead of selected text if (self.typingAttributesInProgress || ![self hasText]) { @@ -450,12 +478,12 @@ - (void)updateToolbarState } else { - int location = [self offsetFromPosition:self.beginningOfDocument toPosition:self.selectedTextRange.start]; - - if (location == self.text.length) - location --; + NSInteger location = (self.selectedRange.length == 0) + ? MAX((int)self.selectedRange.location-1, 0) + : (int)self.selectedRange.location; - [self.toolBar updateStateWithAttributes:[self.attributedText attributesAtIndex:location effectiveRange:nil]]; + NSDictionary *attributes = [self.attributedText attributesAtIndex:location effectiveRange:nil]; + [self.toolBar updateStateWithAttributes:attributes]; } } @@ -630,7 +658,7 @@ - (void)applyBulletListIfApplicable return; NSRange rangeOfPreviousParagraph = [self.attributedText firstParagraphRangeFromTextRange:NSMakeRange(rangeOfCurrentParagraph.location-1, 0)]; - if ([[self.attributedText.string substringFromIndex:rangeOfPreviousParagraph.location] startsWithString:BULLET_STRING]) + if ([[self.attributedText.string substringFromIndex:rangeOfPreviousParagraph.location] hasPrefix:BULLET_STRING]) [self richTextEditorToolbarDidSelectBulletList]; } @@ -640,12 +668,12 @@ - (void)deleteBulletListWhenApplicable if (range.location > 0) { - if ([[[self.attributedText.string substringFromIndex:range.location-1] substringToIndex:1] isEqual:@"•"]) + if ((int)range.location-2 >= 0 && [[self.attributedText.string substringFromIndex:range.location-2] hasPrefix:@"\t•"]) { NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; - [mutableAttributedString deleteCharactersInRange:NSMakeRange(range.location-1, 1)]; + [mutableAttributedString deleteCharactersInRange:NSMakeRange(range.location-2, 2)]; self.attributedText = mutableAttributedString; - [self setSelectedRange:NSMakeRange(range.location-1, 0)]; + [self setSelectedRange:NSMakeRange(range.location-2, 0)]; } } } From e7c27f1d374f971232651ef6446d14a3e1b7c297 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 29 Sep 2013 21:04:12 -0700 Subject: [PATCH 13/42] - Added TextAttachment functionality (Inserting images into RichTextEditor) --- RichTextEditor/Source/RichTextEditor.m | 12 +++++- RichTextEditor/Source/RichTextEditorToolbar.h | 6 ++- RichTextEditor/Source/RichTextEditorToolbar.m | 41 ++++++++++++++++++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 2f0ea34..c66fb66 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -31,7 +31,6 @@ #import "NSAttributedString+RichTextEditor.h" #import "UIView+RichTextEditor.h" -#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) #define RICHTEXTEDITOR_TOOLBAR_HEIGHT 40 #define BULLET_STRING @"\t•\t" @@ -432,6 +431,17 @@ - (void)richTextEditorToolbarDidSelectBulletList } } +- (void)richTextEditorToolbarDidSelectTextAttachment:(UIImage *)textAttachment +{ + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + [attachment setImage:textAttachment]; + NSAttributedString *attributedStringAttachment = [NSAttributedString attributedStringWithAttachment:attachment]; + + NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; + [attributedString insertAttributedString:attributedStringAttachment atIndex:self.selectedRange.location]; + self.attributedText = attributedString; +} + #pragma mark - Private Methods - - (CGRect)frameOfTextAtRange:(NSRange)range diff --git a/RichTextEditor/Source/RichTextEditorToolbar.h b/RichTextEditor/Source/RichTextEditorToolbar.h index 7ecc845..ac69dc8 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.h +++ b/RichTextEditor/Source/RichTextEditorToolbar.h @@ -27,6 +27,8 @@ #import +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) + typedef enum{ RichTextEditorToolbarPresentationStyleModal, RichTextEditorToolbarPresentationStylePopover @@ -54,7 +56,8 @@ typedef enum{ RichTextEditorFeatureParagraphIndentation = 1 << 12, RichTextEditorFeatureParagraphFirstLineIndentation = 1 << 13, RichTextEditorFeatureBulletList = 1 << 14, - RichTextEditorFeatureAll = 1 << 15 + RichTextEditorTextAttachment = 1 << 15, + RichTextEditorFeatureAll = 1 << 16 }RichTextEditorFeature; @protocol RichTextEditorToolbarDelegate @@ -63,6 +66,7 @@ typedef enum{ - (void)richTextEditorToolbarDidSelectUnderline; - (void)richTextEditorToolbarDidSelectStrikeThrough; - (void)richTextEditorToolbarDidSelectBulletList; +- (void)richTextEditorToolbarDidSelectTextAttachment:(UIImage *)textAttachment; - (void)richTextEditorToolbarDidSelectParagraphFirstLineHeadIndent; - (void)richTextEditorToolbarDidSelectParagraphIndentation:(ParagraphIndentation)paragraphIndentation; - (void)richTextEditorToolbarDidSelectFontSize:(NSNumber *)fontSize; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 5337e1d..e918188 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -39,7 +39,7 @@ #define ITEM_TOP_AND_BOTTOM_BORDER 5 #define ITEM_WITH 40 -@interface RichTextEditorToolbar() +@interface RichTextEditorToolbar() @property (nonatomic, strong) id popover; @property (nonatomic, strong) RichTextEditorToggleButton *btnBold; @property (nonatomic, strong) RichTextEditorToggleButton *btnItalic; @@ -57,6 +57,7 @@ @interface RichTextEditorToolbar() Date: Sun, 3 Nov 2013 18:19:55 -0800 Subject: [PATCH 14/42] Fixed #32 Inserting an image loses the attributed and won't apply to text being typed after the image --- RichTextEditor/Source/RichTextEditor.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index c66fb66..80b3890 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -437,8 +437,11 @@ - (void)richTextEditorToolbarDidSelectTextAttachment:(UIImage *)textAttachment [attachment setImage:textAttachment]; NSAttributedString *attributedStringAttachment = [NSAttributedString attributedStringWithAttachment:attachment]; + NSDictionary *previousAttributes = [self dictionaryAtIndex:self.selectedRange.location]; + NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; [attributedString insertAttributedString:attributedStringAttachment atIndex:self.selectedRange.location]; + [attributedString addAttributes:previousAttributes range:NSMakeRange(self.selectedRange.location, 1)]; self.attributedText = attributedString; } From 3d536a2792c43b3cefc8feb531d845693faf4d33 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Mon, 11 Nov 2013 18:29:57 -0800 Subject: [PATCH 15/42] - Overriding setAttributedText & setText and updating toolbar state to make sure that when the text is changed through code the toolbar gets updated --- RichTextEditor/Source/RichTextEditor.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 80b3890..18de0f1 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -155,6 +155,18 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender return [super canPerformAction:action withSender:sender]; } +- (void)setAttributedText:(NSAttributedString *)attributedText +{ + [super setAttributedText:attributedText]; + [self updateToolbarState]; +} + +- (void)setText:(NSString *)text +{ + [super setText:text]; + [self updateToolbarState]; +} + #pragma mark - MenuController Methods - - (void)setupMenuItems From 5e0cd8e21d5d42f627800b599711e749203bdf1f Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Mon, 11 Nov 2013 18:38:19 -0800 Subject: [PATCH 16/42] - We want to update the toolbar when the font changes as well --- RichTextEditor/Source/RichTextEditor.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 18de0f1..c7810ef 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -167,6 +167,12 @@ - (void)setText:(NSString *)text [self updateToolbarState]; } +- (void)setFont:(UIFont *)font +{ + [super setFont:font]; + [self updateToolbarState]; +} + #pragma mark - MenuController Methods - - (void)setupMenuItems From c3464c18a995ebefb11f0cfac78068f2187a49ee Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Dec 2013 10:05:39 -0800 Subject: [PATCH 17/42] - Fixed the problem with the color picker where selecting outside of the color picker causes the color to become black. - Sliding finger to top of the color picker will now select white, and sliding to the bottom will now select black. - Done button was renamed to select - There is now a clear button to completely remove color attributed when needed --- RichTextEditor/Source/RichTextEditor.m | 26 ++++++++++++- .../RichTextEditorColorPickerViewController.m | 39 ++++++++++++------- RichTextEditor/Source/RichTextEditorToolbar.m | 2 +- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index c7810ef..1952dc8 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -271,12 +271,18 @@ - (void)richTextEditorToolbarDidSelectFontWithName:(NSString *)fontName - (void)richTextEditorToolbarDidSelectTextBackgroundColor:(UIColor *)color { - [self applyAttrubutesToSelectedRange:color forKey:NSBackgroundColorAttributeName]; + if (color) + [self applyAttrubutesToSelectedRange:color forKey:NSBackgroundColorAttributeName]; + else + [self removeAttributeForKeyFromSelectedRange:NSBackgroundColorAttributeName]; } - (void)richTextEditorToolbarDidSelectTextForegroundColor:(UIColor *)color { - [self applyAttrubutesToSelectedRange:color forKey:NSForegroundColorAttributeName]; + if (color) + [self applyAttrubutesToSelectedRange:color forKey:NSForegroundColorAttributeName]; + else + [self removeAttributeForKeyFromSelectedRange:NSForegroundColorAttributeName]; } - (void)richTextEditorToolbarDidSelectUnderline @@ -589,6 +595,22 @@ - (void)applyAttributes:(id)attribute forKey:(NSString *)key atRange:(NSRange)ra [self updateToolbarState]; } +- (void)removeAttributeForKey:(NSString *)key atRange:(NSRange)range +{ + NSRange initialRange = self.selectedRange; + + NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; + [attributedString removeAttribute:key range:range]; + self.attributedText = attributedString; + + [self setSelectedRange:initialRange]; +} + +- (void)removeAttributeForKeyFromSelectedRange:(NSString *)key +{ + [self removeAttributeForKey:key atRange:self.selectedRange]; +} + - (void)applyAttrubutesToSelectedRange:(id)attribute forKey:(NSString *)key { [self applyAttributes:attribute forKey:key atRange:self.selectedRange]; diff --git a/RichTextEditor/Source/RichTextEditorColorPickerViewController.m b/RichTextEditor/Source/RichTextEditorColorPickerViewController.m index 929ee6a..f6ec8d6 100644 --- a/RichTextEditor/Source/RichTextEditorColorPickerViewController.m +++ b/RichTextEditor/Source/RichTextEditorColorPickerViewController.m @@ -37,19 +37,19 @@ - (void)viewDidLoad self.view.backgroundColor = [UIColor whiteColor]; - UIButton *btnClose = [[UIButton alloc] initWithFrame:CGRectMake(5, 5, 60, 30)]; - [btnClose addTarget:self action:@selector(closeSelected:) forControlEvents:UIControlEventTouchUpInside]; - [btnClose.titleLabel setFont:[UIFont boldSystemFontOfSize:12]]; - [btnClose setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; - [btnClose setTitle:@"Close" forState:UIControlStateNormal]; - [self.view addSubview:btnClose]; - - UIButton *btnDone = [[UIButton alloc] initWithFrame:CGRectMake(65, 5, 60, 30)]; + UIButton *btnDone = [[UIButton alloc] initWithFrame:CGRectMake(5, 5, 60, 30)]; [btnDone addTarget:self action:@selector(doneSelected:) forControlEvents:UIControlEventTouchUpInside]; [btnDone.titleLabel setFont:[UIFont boldSystemFontOfSize:12]]; [btnDone setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; - [btnDone setTitle:@"Done" forState:UIControlStateNormal]; + [btnDone setTitle:@"Select" forState:UIControlStateNormal]; [self.view addSubview:btnDone]; + + UIButton *btnClear = [[UIButton alloc] initWithFrame:CGRectMake(65, 5, 60, 30)]; + [btnClear addTarget:self action:@selector(clearSelected:) forControlEvents:UIControlEventTouchUpInside]; + [btnClear.titleLabel setFont:[UIFont boldSystemFontOfSize:12]]; + [btnClear setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + [btnClear setTitle:@"Clear" forState:UIControlStateNormal]; + [self.view addSubview:btnClear]; self.selectedColorView = [[UIView alloc] initWithFrame:CGRectMake(self.view.frame.size.width - 35 - 5, 5, 35, 30)]; self.selectedColorView.backgroundColor = [UIColor blackColor]; @@ -62,7 +62,7 @@ - (void)viewDidLoad self.colorsImageView.frame = CGRectMake(2, 40, self.view.frame.size.width-4, self.view.frame.size.height - 40 - 2); self.colorsImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.colorsImageView.layer.borderColor = [UIColor lightGrayColor].CGColor; - self.colorsImageView.layer.borderWidth = 1; + self.colorsImageView.layer.borderWidth = 0; [self.view addSubview:self.colorsImageView]; if ([self.dataSource richTextEditorColorPickerViewControllerShouldDisplayToolbar]) @@ -75,7 +75,7 @@ - (void)viewDidLoad target:nil action:nil]; - UIBarButtonItem *doneItem = [[UIBarButtonItem alloc] initWithTitle:@"Done" + UIBarButtonItem *doneItem = [[UIBarButtonItem alloc] initWithTitle:@"Select" style:UIBarButtonItemStyleDone target:self action:@selector(doneSelected:)]; @@ -85,9 +85,14 @@ - (void)viewDidLoad target:self action:@selector(closeSelected:)]; + UIBarButtonItem *clearItem = [[UIBarButtonItem alloc] initWithTitle:@"Clear" + style:UIBarButtonItemStyleDone + target:self + action:@selector(clearSelected:)]; + UIBarButtonItem *selectedColorItem = [[UIBarButtonItem alloc] initWithCustomView:self.selectedColorView]; - [toolbar setItems:@[doneItem, selectedColorItem, flexibleSpaceItem , closeItem]]; + [toolbar setItems:@[doneItem, clearItem, flexibleSpaceItem ,selectedColorItem, flexibleSpaceItem , closeItem]]; [self.view addSubview:toolbar]; self.colorsImageView.frame = CGRectMake(2, 46, self.view.frame.size.width-4, self.view.frame.size.height - 46 - 2); @@ -100,7 +105,10 @@ - (void)viewDidLoad - (void)populateColorsForPoint:(CGPoint)point { - self.selectedColorView.backgroundColor = [self.colorsImageView colorOfPoint:point]; + CGPoint pointInView = [self.colorsImageView convertPoint:point toView:self.colorsImageView]; + + if (CGRectContainsPoint(self.colorsImageView.bounds, pointInView)) + self.selectedColorView.backgroundColor = [self.colorsImageView colorOfPoint:pointInView]; } #pragma mark - IBActions - @@ -115,6 +123,11 @@ - (IBAction)closeSelected:(id)sender [self.delegate richTextEditorColorPickerViewControllerDidSelectClose]; } +- (IBAction)clearSelected:(id)sender +{ + [self.delegate richTextEditorColorPickerViewControllerDidSelectColor:nil withAction:self.action]; +} + #pragma mark - Touch Detection - - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index e918188..115eae4 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -39,7 +39,7 @@ #define ITEM_TOP_AND_BOTTOM_BORDER 5 #define ITEM_WITH 40 -@interface RichTextEditorToolbar() +@interface RichTextEditorToolbar() @property (nonatomic, strong) id popover; @property (nonatomic, strong) RichTextEditorToggleButton *btnBold; @property (nonatomic, strong) RichTextEditorToggleButton *btnItalic; From 7a6323d73aa43af0a62913c6286e80762adaf370 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sat, 8 Feb 2014 09:07:57 -0800 Subject: [PATCH 18/42] - On iPhone (iOS7) when modal is presented, textView loses focus, when modal is dismissed textView doesn't become first responder automatically So there is now a new delegate that let's the RichTextEditor to become first responder after the modal is dismissed --- RichTextEditor/Source/RichTextEditor.m | 17 ++++++++++++++--- RichTextEditor/Source/RichTextEditorToolbar.h | 1 + RichTextEditor/Source/RichTextEditorToolbar.m | 6 ++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 1952dc8..864b01c 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -247,6 +247,12 @@ - (void)setBorderWidth:(CGFloat)borderWidth #pragma mark - RichTextEditorToolbarDelegate Methods - +- (void)richTextEditorToolbarDidDismissViewController +{ + if (![self isFirstResponder]) + [self becomeFirstResponder]; +} + - (void)richTextEditorToolbarDidSelectBold { UIFont *font = [self fontAtIndex:self.selectedRange.location]; @@ -515,9 +521,14 @@ - (void)updateToolbarState } else { - NSInteger location = (self.selectedRange.length == 0) - ? MAX((int)self.selectedRange.location-1, 0) - : (int)self.selectedRange.location; + NSInteger location = 0; + + if (self.selectedRange.location != NSNotFound) + { + location = (self.selectedRange.length == 0) + ? MAX((int)self.selectedRange.location-1, 0) + : (int)self.selectedRange.location; + } NSDictionary *attributes = [self.attributedText attributesAtIndex:location effectiveRange:nil]; [self.toolBar updateStateWithAttributes:attributes]; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.h b/RichTextEditor/Source/RichTextEditorToolbar.h index ac69dc8..d6a7145 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.h +++ b/RichTextEditor/Source/RichTextEditorToolbar.h @@ -61,6 +61,7 @@ typedef enum{ }RichTextEditorFeature; @protocol RichTextEditorToolbarDelegate +- (void)richTextEditorToolbarDidDismissViewController; - (void)richTextEditorToolbarDidSelectBold; - (void)richTextEditorToolbarDidSelectItalic; - (void)richTextEditorToolbarDidSelectUnderline; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 115eae4..a3ac90c 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -595,6 +595,8 @@ - (void)dismissViewController { [self.popover dismissPopoverAnimated:YES]; } + + [self.delegate richTextEditorToolbarDidDismissViewController]; } #pragma mark - RichTextEditorColorPickerViewControllerDelegate & RichTextEditorColorPickerViewControllerDataSource Methods - @@ -675,12 +677,12 @@ - (void)imagePickerController:(UIImagePickerController *)picker didFinishPicking { UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; [self.delegate richTextEditorToolbarDidSelectTextAttachment:image]; - [self.popover dismissPopoverAnimated:YES]; + [self dismissViewController]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - [self.popover dismissPopoverAnimated:YES]; + [self dismissViewController]; } @end From 6fd4ad99240aa8cb15dc427700ed0ae37ecdb055 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sat, 8 Feb 2014 09:44:31 -0800 Subject: [PATCH 19/42] - Added 3 new datasource methods to allow the user to pass a custom colorPicker, fontPicker, or a fontSizePicker to richTextEditor in order to override the use of default pickers --- RichTextEditor.xcodeproj/project.pbxproj | 6 ++++ RichTextEditor/Source/RichTextEditor.h | 3 ++ RichTextEditor/Source/RichTextEditor.m | 24 ++++++++++++++ .../Source/RichTextEditorColorPicker.h | 31 +++++++++++++++++++ .../RichTextEditorColorPickerViewController.h | 17 ++-------- .../Source/RichTextEditorFontPicker.h | 26 ++++++++++++++++ .../RichTextEditorFontPickerViewController.h | 13 ++------ .../RichTextEditorFontPickerViewController.m | 5 ++- .../Source/RichTextEditorFontSizePicker.h | 26 ++++++++++++++++ ...chTextEditorFontSizePickerViewController.h | 13 ++------ RichTextEditor/Source/RichTextEditorToolbar.h | 6 ++++ RichTextEditor/Source/RichTextEditorToolbar.m | 25 ++++++++++++--- 12 files changed, 150 insertions(+), 45 deletions(-) create mode 100644 RichTextEditor/Source/RichTextEditorColorPicker.h create mode 100644 RichTextEditor/Source/RichTextEditorFontPicker.h create mode 100644 RichTextEditor/Source/RichTextEditorFontSizePicker.h diff --git a/RichTextEditor.xcodeproj/project.pbxproj b/RichTextEditor.xcodeproj/project.pbxproj index 236b1e9..1ebf1a3 100644 --- a/RichTextEditor.xcodeproj/project.pbxproj +++ b/RichTextEditor.xcodeproj/project.pbxproj @@ -216,6 +216,9 @@ 15C4133517A48C6D0073D047 /* buttoncenter.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buttoncenter.png; sourceTree = ""; }; 15C4133617A48C6D0073D047 /* buttonleft.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buttonleft.png; sourceTree = ""; }; 15C4133717A48C6D0073D047 /* buttonright.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buttonright.png; sourceTree = ""; }; + 15C9AC7818A69C69006E6F27 /* RichTextEditorColorPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RichTextEditorColorPicker.h; sourceTree = ""; }; + 15C9AC7918A69E12006E6F27 /* RichTextEditorFontPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RichTextEditorFontPicker.h; sourceTree = ""; }; + 15C9AC7A18A69E6E006E6F27 /* RichTextEditorFontSizePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RichTextEditorFontSizePicker.h; sourceTree = ""; }; 1AFAED6E17C7F9CA00E450B0 /* strikethrough@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "strikethrough@2x.png"; sourceTree = ""; }; 1AFAED6F17C7F9CA00E450B0 /* italic@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "italic@2x.png"; sourceTree = ""; }; 1AFAED7017C7F9CA00E450B0 /* underline@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "underline@2x.png"; sourceTree = ""; }; @@ -268,10 +271,13 @@ 153F9585179F9953005B2487 /* RichTextEditorToolbar.m */, 15B6E20D17A4C2820015867C /* RichTextEditorToggleButton.h */, 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */, + 15C9AC7818A69C69006E6F27 /* RichTextEditorColorPicker.h */, 153F957D179F9953005B2487 /* RichTextEditorColorPickerViewController.h */, 153F957E179F9953005B2487 /* RichTextEditorColorPickerViewController.m */, + 15C9AC7918A69E12006E6F27 /* RichTextEditorFontPicker.h */, 153F957F179F9953005B2487 /* RichTextEditorFontPickerViewController.h */, 153F9580179F9953005B2487 /* RichTextEditorFontPickerViewController.m */, + 15C9AC7A18A69E6E006E6F27 /* RichTextEditorFontSizePicker.h */, 153F9581179F9953005B2487 /* RichTextEditorFontSizePickerViewController.h */, 153F9582179F9953005B2487 /* RichTextEditorFontSizePickerViewController.m */, 153F9583179F9953005B2487 /* RichTextEditorPopover.h */, diff --git a/RichTextEditor/Source/RichTextEditor.h b/RichTextEditor/Source/RichTextEditor.h index b23f4d3..db84e3f 100644 --- a/RichTextEditor/Source/RichTextEditor.h +++ b/RichTextEditor/Source/RichTextEditor.h @@ -39,6 +39,9 @@ - (RichTextEditorFeature)featuresEnabledForRichTextEditor:(RichTextEditor *)richTextEditor; - (BOOL)shouldDisplayToolbarForRichTextEditor:(RichTextEditor *)richTextEditor; - (BOOL)shouldDisplayRichTextOptionsInMenuControllerForRichTextEditor:(RichTextEditor *)richTextEdiotor; +- (UIViewController *)colorPickerForRichTextEditor:(RichTextEditor *)richTextEdiotor withAction:(RichTextEditorColorPickerAction)action; +- (UIViewController *)fontPickerForRichTextEditor:(RichTextEditor *)richTextEdiotor; +- (UIViewController *)fontSizePickerForRichTextEditor:(RichTextEditor *)richTextEdiotor; @end @interface RichTextEditor : UITextView diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 864b01c..43f4534 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -475,6 +475,30 @@ - (void)richTextEditorToolbarDidSelectTextAttachment:(UIImage *)textAttachment self.attributedText = attributedString; } +- (UIViewController *)colorPickerForRichTextEditorToolbarWithAction:(RichTextEditorColorPickerAction)action +{ + if ([self.dataSource respondsToSelector:@selector(colorPickerForRichTextEditor:forAction:)]) + return [self.dataSource colorPickerForRichTextEditor:self withAction:action]; + + return nil; +} + +- (UIViewController *)fontPickerForRichTextEditorToolbar +{ + if ([self.dataSource respondsToSelector:@selector(fontPickerForRichTextEditor:)]) + return [self.dataSource fontPickerForRichTextEditor:self]; + + return nil; +} + +- (UIViewController *)fontSizePickerForRichTextEditorToolbar +{ + if ([self.dataSource respondsToSelector:@selector(fontSizePickerForRichTextEditor:)]) + return [self.dataSource fontSizePickerForRichTextEditor:self]; + + return nil; +} + #pragma mark - Private Methods - - (CGRect)frameOfTextAtRange:(NSRange)range diff --git a/RichTextEditor/Source/RichTextEditorColorPicker.h b/RichTextEditor/Source/RichTextEditorColorPicker.h new file mode 100644 index 0000000..6543efb --- /dev/null +++ b/RichTextEditor/Source/RichTextEditorColorPicker.h @@ -0,0 +1,31 @@ +// +// RichTextEditorColorPicker.h +// RichTextEditor +// +// Created by Aryan Gh on 2/8/14. +// Copyright (c) 2014 Aryan Ghassemi. All rights reserved. +// + +#import + +typedef enum { + RichTextEditorColorPickerActionTextForegroudColor, + RichTextEditorColorPickerActionTextBackgroundColor +}RichTextEditorColorPickerAction; + +@protocol RichTextEditorColorPickerViewControllerDelegate +- (void)richTextEditorColorPickerViewControllerDidSelectColor:(UIColor *)color withAction:(RichTextEditorColorPickerAction)action; +- (void)richTextEditorColorPickerViewControllerDidSelectClose; +@end + +@protocol RichTextEditorColorPickerViewControllerDataSource +- (BOOL)richTextEditorColorPickerViewControllerShouldDisplayToolbar; +@end + +@protocol RichTextEditorColorPicker + +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id dataSource; +@property (nonatomic, assign) RichTextEditorColorPickerAction action; + +@end diff --git a/RichTextEditor/Source/RichTextEditorColorPickerViewController.h b/RichTextEditor/Source/RichTextEditorColorPickerViewController.h index 0bdaeb5..b844139 100644 --- a/RichTextEditor/Source/RichTextEditorColorPickerViewController.h +++ b/RichTextEditor/Source/RichTextEditorColorPickerViewController.h @@ -28,22 +28,9 @@ #import #import #import "UIView+RichTextEditor.h" +#import "RichTextEditorColorPicker.h" -typedef enum { - RichTextEditorColorPickerActionTextForegroudColor, - RichTextEditorColorPickerActionTextBackgroundColor -}RichTextEditorColorPickerAction; - -@protocol RichTextEditorColorPickerViewControllerDelegate -- (void)richTextEditorColorPickerViewControllerDidSelectColor:(UIColor *)color withAction:(RichTextEditorColorPickerAction)action; -- (void)richTextEditorColorPickerViewControllerDidSelectClose; -@end - -@protocol RichTextEditorColorPickerViewControllerDataSource -- (BOOL)richTextEditorColorPickerViewControllerShouldDisplayToolbar; -@end - -@interface RichTextEditorColorPickerViewController : UIViewController +@interface RichTextEditorColorPickerViewController : UIViewController @property (nonatomic, weak) id delegate; @property (nonatomic, weak) id dataSource; diff --git a/RichTextEditor/Source/RichTextEditorFontPicker.h b/RichTextEditor/Source/RichTextEditorFontPicker.h new file mode 100644 index 0000000..c3262a6 --- /dev/null +++ b/RichTextEditor/Source/RichTextEditorFontPicker.h @@ -0,0 +1,26 @@ +// +// RichTextEditorFontPicker.h +// RichTextEditor +// +// Created by Aryan Gh on 2/8/14. +// Copyright (c) 2014 Aryan Ghassemi. All rights reserved. +// + +#import + +@protocol RichTextEditorFontPickerViewControllerDelegate +- (void)richTextEditorFontPickerViewControllerDidSelectFontWithName:(NSString *)fontName; +- (void)richTextEditorFontPickerViewControllerDidSelectClose; +@end + +@protocol RichTextEditorFontPickerViewControllerDataSource +- (NSArray *)richTextEditorFontPickerViewControllerCustomFontFamilyNamesForSelection; +- (BOOL)richTextEditorFontPickerViewControllerShouldDisplayToolbar; +@end + +@protocol RichTextEditorFontPicker + +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id dataSource; + +@end diff --git a/RichTextEditor/Source/RichTextEditorFontPickerViewController.h b/RichTextEditor/Source/RichTextEditorFontPickerViewController.h index 39734a3..6115435 100644 --- a/RichTextEditor/Source/RichTextEditorFontPickerViewController.h +++ b/RichTextEditor/Source/RichTextEditorFontPickerViewController.h @@ -26,18 +26,9 @@ // THE SOFTWARE. #import +#import "RichTextEditorFontPicker.h" -@protocol RichTextEditorFontPickerViewControllerDelegate -- (void)richTextEditorFontPickerViewControllerDidSelectFontWithName:(NSString *)fontName; -- (void)richTextEditorFontPickerViewControllerDidSelectClose; -@end - -@protocol RichTextEditorFontPickerViewControllerDataSource -- (NSArray *)richTextEditorFontPickerViewControllerCustomFontFamilyNamesForSelection; -- (BOOL)richTextEditorFontPickerViewControllerShouldDisplayToolbar; -@end - -@interface RichTextEditorFontPickerViewController : UIViewController +@interface RichTextEditorFontPickerViewController : UIViewController @property (nonatomic, weak) id delegate; @property (nonatomic, weak) id dataSource; diff --git a/RichTextEditor/Source/RichTextEditorFontPickerViewController.m b/RichTextEditor/Source/RichTextEditorFontPickerViewController.m index c31bbb4..17064d3 100644 --- a/RichTextEditor/Source/RichTextEditorFontPickerViewController.m +++ b/RichTextEditor/Source/RichTextEditorFontPickerViewController.m @@ -35,11 +35,10 @@ - (void)viewDidLoad NSArray *customizedFontFamilies = [self.dataSource richTextEditorFontPickerViewControllerCustomFontFamilyNamesForSelection]; - if (customizedFontFamilies) { + if (customizedFontFamilies) self.fontNames = customizedFontFamilies; - } else { + else self.fontNames = [[UIFont familyNames] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; - } if ([self.dataSource richTextEditorFontPickerViewControllerShouldDisplayToolbar]) { diff --git a/RichTextEditor/Source/RichTextEditorFontSizePicker.h b/RichTextEditor/Source/RichTextEditorFontSizePicker.h new file mode 100644 index 0000000..f1917db --- /dev/null +++ b/RichTextEditor/Source/RichTextEditorFontSizePicker.h @@ -0,0 +1,26 @@ +// +// RichTextEditorFontSizePicker.h +// RichTextEditor +// +// Created by Aryan Gh on 2/8/14. +// Copyright (c) 2014 Aryan Ghassemi. All rights reserved. +// + +#import + +@protocol RichTextEditorFontSizePickerViewControllerDelegate +- (void)richTextEditorFontSizePickerViewControllerDidSelectFontSize:(NSNumber *)fontSize; +- (void)richTextEditorFontSizePickerViewControllerDidSelectClose; +@end + +@protocol RichTextEditorFontSizePickerViewControllerDataSource +- (BOOL)richTextEditorFontSizePickerViewControllerShouldDisplayToolbar; +- (NSArray *)richTextEditorFontSizePickerViewControllerCustomFontSizesForSelection; +@end + +@protocol RichTextEditorFontSizePicker + +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id dataSource; + +@end diff --git a/RichTextEditor/Source/RichTextEditorFontSizePickerViewController.h b/RichTextEditor/Source/RichTextEditorFontSizePickerViewController.h index 70d6341..8ba4001 100644 --- a/RichTextEditor/Source/RichTextEditorFontSizePickerViewController.h +++ b/RichTextEditor/Source/RichTextEditorFontSizePickerViewController.h @@ -26,18 +26,9 @@ // THE SOFTWARE. #import +#import "RichTextEditorFontSizePicker.h" -@protocol RichTextEditorFontSizePickerViewControllerDelegate -- (void)richTextEditorFontSizePickerViewControllerDidSelectFontSize:(NSNumber *)fontSize; -- (void)richTextEditorFontSizePickerViewControllerDidSelectClose; -@end - -@protocol RichTextEditorFontSizePickerViewControllerDataSource -- (BOOL)richTextEditorFontSizePickerViewControllerShouldDisplayToolbar; -- (NSArray *)richTextEditorFontSizePickerViewControllerCustomFontSizesForSelection; -@end - -@interface RichTextEditorFontSizePickerViewController : UIViewController +@interface RichTextEditorFontSizePickerViewController : UIViewController @property (nonatomic, weak) id delegate; @property (nonatomic, weak) id dataSource; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.h b/RichTextEditor/Source/RichTextEditorToolbar.h index d6a7145..41b3b94 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.h +++ b/RichTextEditor/Source/RichTextEditorToolbar.h @@ -26,6 +26,9 @@ // THE SOFTWARE. #import +#import "RichTextEditorColorPicker.h" +#import "RichTextEditorFontPicker.h" +#import "RichTextEditorFontSizePicker.h" #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) @@ -85,6 +88,9 @@ typedef enum{ - (UIModalTransitionStyle)modalTransitionStyleForRichTextEditorToolbar; - (UIViewController *)firsAvailableViewControllerForRichTextEditorToolbar; - (RichTextEditorFeature)featuresEnabledForRichTextEditorToolbar; +- (UIViewController *)colorPickerForRichTextEditorToolbarWithAction:(RichTextEditorColorPickerAction)action; +- (UIViewController *)fontPickerForRichTextEditorToolbar; +- (UIViewController *)fontSizePickerForRichTextEditorToolbar; @end @interface RichTextEditorToolbar : UIScrollView diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index a3ac90c..9e7eae3 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -181,7 +181,11 @@ - (void)paragraphHeadIndentOutdentSelected:(UIButton *)sender - (void)fontSizeSelected:(UIButton *)sender { - RichTextEditorFontSizePickerViewController *fontSizePicker = [[RichTextEditorFontSizePickerViewController alloc] init]; + UIViewController *fontSizePicker = [self.dataSource fontSizePickerForRichTextEditorToolbar]; + + if (!fontSizePicker) + fontSizePicker = [[RichTextEditorFontSizePickerViewController alloc] init]; + fontSizePicker.delegate = self; fontSizePicker.dataSource = self; [self presentViewController:fontSizePicker fromView:sender]; @@ -189,8 +193,11 @@ - (void)fontSizeSelected:(UIButton *)sender - (void)fontSelected:(UIButton *)sender { - RichTextEditorFontPickerViewController *fontPicker= [[RichTextEditorFontPickerViewController alloc] init]; - fontPicker.fontNames = [self.dataSource fontFamilySelectionForRichTextEditorToolbar]; + UIViewController *fontPicker = [self.dataSource fontPickerForRichTextEditorToolbar]; + + if (!fontPicker) + fontPicker= [[RichTextEditorFontPickerViewController alloc] init]; + fontPicker.delegate = self; fontPicker.dataSource = self; [self presentViewController:fontPicker fromView:sender]; @@ -198,7 +205,11 @@ - (void)fontSelected:(UIButton *)sender - (void)textBackgroundColorSelected:(UIButton *)sender { - RichTextEditorColorPickerViewController *colorPicker = [[RichTextEditorColorPickerViewController alloc] init]; + UIViewController *colorPicker = [self.dataSource colorPickerForRichTextEditorToolbarWithAction:RichTextEditorColorPickerActionTextBackgroundColor]; + + if (!colorPicker) + colorPicker = [[RichTextEditorColorPickerViewController alloc] init]; + colorPicker.action = RichTextEditorColorPickerActionTextBackgroundColor; colorPicker.delegate = self; colorPicker.dataSource = self; @@ -207,7 +218,11 @@ - (void)textBackgroundColorSelected:(UIButton *)sender - (void)textForegroundColorSelected:(UIButton *)sender { - RichTextEditorColorPickerViewController *colorPicker = [[RichTextEditorColorPickerViewController alloc] init]; + UIViewController *colorPicker = [self.dataSource colorPickerForRichTextEditorToolbarWithAction:RichTextEditorColorPickerActionTextForegroudColor]; + + if (!colorPicker) + colorPicker = [[RichTextEditorColorPickerViewController alloc] init]; + colorPicker.action = RichTextEditorColorPickerActionTextForegroudColor; colorPicker.delegate = self; colorPicker.dataSource = self; From eea13569cb50e3283fb0a124b4d62467a5e37d49 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sat, 8 Feb 2014 10:41:31 -0800 Subject: [PATCH 20/42] - When backspace is selected and bullet is removed, the paragraph head indent needs to be set to 0 so that new lines won't have bullet indent --- RichTextEditor/Source/RichTextEditor.m | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 43f4534..f9ab1f0 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -758,10 +758,20 @@ - (void)deleteBulletListWhenApplicable { if ((int)range.location-2 >= 0 && [[self.attributedText.string substringFromIndex:range.location-2] hasPrefix:@"\t•"]) { + // Get rid of bullet string NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; [mutableAttributedString deleteCharactersInRange:NSMakeRange(range.location-2, 2)]; self.attributedText = mutableAttributedString; - [self setSelectedRange:NSMakeRange(range.location-2, 0)]; + NSRange newRange = NSMakeRange(range.location-2, 0); + [self setSelectedRange:newRange]; + + // Get rid of bullet indentation + NSRange rangeOfParagraph = [self.attributedText firstParagraphRangeFromTextRange:newRange]; + NSDictionary *dictionary = [self dictionaryAtIndex:newRange.location]; + NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = 0; + [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:rangeOfParagraph]; } } } From cdd5e6d16556cddcd85dafb9712691d7d8939d37 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 15 Sep 2013 14:54:16 -0700 Subject: [PATCH 21/42] - Initial commit for bullet list TODO: - Make bullets bold - Calculate width of bullet-string according to the font and set headIndent and firstLineHeadIndent according to this width --- .../Categories/NSString+RichTextEditor.h | 15 ++++++++ .../Categories/NSString+RichTextEditor.m | 21 +++++++++++ RichTextEditor/Source/RichTextEditor.m | 35 +++++++++++++++++-- RichTextEditor/Source/RichTextEditorToolbar.h | 5 +-- RichTextEditor/Source/RichTextEditorToolbar.m | 19 ++++++---- 5 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 RichTextEditor/Source/Categories/NSString+RichTextEditor.h create mode 100644 RichTextEditor/Source/Categories/NSString+RichTextEditor.m diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h new file mode 100644 index 0000000..d48c4be --- /dev/null +++ b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h @@ -0,0 +1,15 @@ +// +// NSString+RichTextEditor.h +// RichTextEditor +// +// Created by Aryan Gh on 9/15/13. +// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. +// + +#import + +@interface NSString (RichTextEditor) + +- (BOOL)startsWithString:(NSString *)string; + +@end diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m new file mode 100644 index 0000000..e48be01 --- /dev/null +++ b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m @@ -0,0 +1,21 @@ +// +// NSString+RichTextEditor.m +// RichTextEditor +// +// Created by Aryan Gh on 9/15/13. +// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. +// + +#import "NSString+RichTextEditor.h" + +@implementation NSString (RichTextEditor) + +- (BOOL)startsWithString:(NSString *)string +{ + if (!string.length || string.length > self.length) + return NO; + + return ([[self substringToIndex:string.length] isEqualToString:string]) ? YES : NO; +} + +@end diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 86231e9..4f0272d 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -29,9 +29,11 @@ #import #import "UIFont+RichTextEditor.h" #import "NSAttributedString+RichTextEditor.h" +#import "NSString+RichTextEditor.h" #import "UIView+RichTextEditor.h" #define RICHTEXTEDITOR_TOOLBAR_HEIGHT 40 +#define BULLET_STRING @"•\t" @interface RichTextEditor() @property (nonatomic, strong) RichTextEditorToolbar *toolBar; @@ -317,9 +319,38 @@ - (void)richTextEditorToolbarDidSelectTextAlignment:(NSTextAlignment)textAlignme }]; } -- (void)richTextEditorToolbarDidSelectBulletPoint +- (void)richTextEditorToolbarDidSelectBulletList { - // TODO: implement this + NSRange range = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; + NSMutableAttributedString *currentAttributedString = [self.attributedText mutableCopy]; + NSDictionary *dictionary = [self dictionaryAtIndex:range.location]; + NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; + + if (!paragraphStyle) + paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + + if ([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING]) + { + range = NSMakeRange(range.location, range.length-2); + + [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, 2)]; + + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = 0; + } + else + { + range = NSMakeRange(range.location, range.length+2); + + NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; + [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; + + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = 27; + } + + self.attributedText = currentAttributedString; + [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:range]; } #pragma mark - Private Methods - diff --git a/RichTextEditor/Source/RichTextEditorToolbar.h b/RichTextEditor/Source/RichTextEditorToolbar.h index ff6be4d..7ecc845 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.h +++ b/RichTextEditor/Source/RichTextEditorToolbar.h @@ -53,7 +53,8 @@ typedef enum{ RichTextEditorFeatureTextForegroundColor = 1 << 11, RichTextEditorFeatureParagraphIndentation = 1 << 12, RichTextEditorFeatureParagraphFirstLineIndentation = 1 << 13, - RichTextEditorFeatureAll = 1 << 14 + RichTextEditorFeatureBulletList = 1 << 14, + RichTextEditorFeatureAll = 1 << 15 }RichTextEditorFeature; @protocol RichTextEditorToolbarDelegate @@ -61,7 +62,7 @@ typedef enum{ - (void)richTextEditorToolbarDidSelectItalic; - (void)richTextEditorToolbarDidSelectUnderline; - (void)richTextEditorToolbarDidSelectStrikeThrough; -- (void)richTextEditorToolbarDidSelectBulletPoint; +- (void)richTextEditorToolbarDidSelectBulletList; - (void)richTextEditorToolbarDidSelectParagraphFirstLineHeadIndent; - (void)richTextEditorToolbarDidSelectParagraphIndentation:(ParagraphIndentation)paragraphIndentation; - (void)richTextEditorToolbarDidSelectFontSize:(NSNumber *)fontSize; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 7cc443d..93ae4d8 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -56,7 +56,7 @@ @interface RichTextEditorToolbar() Date: Sun, 15 Sep 2013 20:34:19 -0700 Subject: [PATCH 22/42] - Calculating the width of the bullet string according to the font and setting it as paragraph indentation to keep all lines in bullet list aligned --- RichTextEditor/Source/RichTextEditor.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 4f0272d..7fc666c 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -344,9 +344,12 @@ - (void)richTextEditorToolbarDidSelectBulletList NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; + CGSize expectedStringSize = [BULLET_STRING sizeWithFont:[dictionary objectForKey:NSFontAttributeName] + constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT) + lineBreakMode:NSLineBreakByWordWrapping]; paragraphStyle.firstLineHeadIndent = 0; - paragraphStyle.headIndent = 27; + paragraphStyle.headIndent = expectedStringSize.width; } self.attributedText = currentAttributedString; From 971de19a6a94b66f77cdc86b37298e8f802579eb Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Fri, 20 Sep 2013 00:55:28 -0700 Subject: [PATCH 23/42] - Added ability to create bullet list by selecting multiple paragraphs at a time --- RichTextEditor/Source/RichTextEditor.m | 68 +++++++++++++++----------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 7fc666c..6b95ba5 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -321,39 +321,51 @@ - (void)richTextEditorToolbarDidSelectTextAlignment:(NSTextAlignment)textAlignme - (void)richTextEditorToolbarDidSelectBulletList { - NSRange range = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; - NSMutableAttributedString *currentAttributedString = [self.attributedText mutableCopy]; - NSDictionary *dictionary = [self dictionaryAtIndex:range.location]; - NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; - - if (!paragraphStyle) - paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; + __block NSInteger rangeOffset = 0; - if ([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING]) - { - range = NSMakeRange(range.location, range.length-2); - - [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, 2)]; + [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ + NSRange range = NSMakeRange(paragraphRange.location + rangeOffset, paragraphRange.length); + NSMutableAttributedString *currentAttributedString = [self.attributedText mutableCopy]; + NSDictionary *dictionary = [self dictionaryAtIndex:range.location]; + NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; - paragraphStyle.firstLineHeadIndent = 0; - paragraphStyle.headIndent = 0; - } - else - { - range = NSMakeRange(range.location, range.length+2); + if (!paragraphStyle) + paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; - [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; - CGSize expectedStringSize = [BULLET_STRING sizeWithFont:[dictionary objectForKey:NSFontAttributeName] - constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT) - lineBreakMode:NSLineBreakByWordWrapping]; + if (([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING])) + { + range = NSMakeRange(range.location, range.length-2); + + [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, 2)]; + + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = 0; + + rangeOffset = rangeOffset - 2; + } + else + { + range = NSMakeRange(range.location, range.length+2); + + NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; + [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; + CGSize expectedStringSize = [BULLET_STRING sizeWithFont:[dictionary objectForKey:NSFontAttributeName] + constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT) + lineBreakMode:NSLineBreakByWordWrapping]; + + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = expectedStringSize.width; + + rangeOffset = rangeOffset + 2; + } - paragraphStyle.firstLineHeadIndent = 0; - paragraphStyle.headIndent = expectedStringSize.width; - } + self.attributedText = currentAttributedString; + [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:range]; + }]; - self.attributedText = currentAttributedString; - [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:range]; + NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; + [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; } #pragma mark - Private Methods - From b32a4e06b50afb71f2fd8225f2b785f64f5b1877 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 14:25:28 -0700 Subject: [PATCH 24/42] - Detect whether bullet should be inserted or not based on the state of the first paragraph - Making the inserted bullet bold - Fixed a memory leak --- .../Categories/NSString+RichTextEditor.h | 19 ++++++++++ .../Categories/NSString+RichTextEditor.m | 19 ++++++++++ .../Source/Categories/UIFont+RichTextEditor.m | 1 + RichTextEditor/Source/RichTextEditor.m | 36 ++++++++++++++++--- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h index d48c4be..2667f4c 100644 --- a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h +++ b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h @@ -5,6 +5,25 @@ // Created by Aryan Gh on 9/15/13. // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. // +// https://github.com/aryaxt/iOS-Rich-Text-Editor +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. #import diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m index e48be01..2aa80c2 100644 --- a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m @@ -5,6 +5,25 @@ // Created by Aryan Gh on 9/15/13. // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. // +// https://github.com/aryaxt/iOS-Rich-Text-Editor +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. #import "NSString+RichTextEditor.h" diff --git a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m index 10b95ce..fd21865 100644 --- a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m @@ -61,6 +61,7 @@ + (UIFont *)fontWithName:(NSString *)name size:(CGFloat)size boldTrait:(BOOL)isB if (newFontRef) { NSString *fontNameKey = (__bridge NSString *)(CTFontCopyName(newFontRef, kCTFontPostScriptNameKey)); + CFRelease(newFontRef); return [UIFont fontWithName:fontNameKey size:CTFontGetSize(newFontRef)]; } diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 6b95ba5..12f6210 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -322,6 +322,9 @@ - (void)richTextEditorToolbarDidSelectTextAlignment:(NSTextAlignment)textAlignme - (void)richTextEditorToolbarDidSelectBulletList { NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; + NSRange rangeOfFirstParagraphRange = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; + BOOL firstParagraphHasBullet = ([[[self.attributedText string] substringFromIndex:rangeOfFirstParagraphRange.location] startsWithString:BULLET_STRING]) ? YES: NO; + __block NSInteger rangeOffset = 0; [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ @@ -333,7 +336,12 @@ - (void)richTextEditorToolbarDidSelectBulletList if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - if (([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING])) + BOOL currentParagraphHasBullet = ([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING]) ? YES : NO; + + if (firstParagraphHasBullet != currentParagraphHasBullet) + return; + + if (currentParagraphHasBullet) { range = NSMakeRange(range.location, range.length-2); @@ -348,8 +356,20 @@ - (void)richTextEditorToolbarDidSelectBulletList { range = NSMakeRange(range.location, range.length+2); - NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:dictionary]; + // The bullet should be bold + NSMutableDictionary *bulletDictionary = [dictionary mutableCopy]; + UIFont *font = [bulletDictionary objectForKey:NSFontAttributeName]; + UIFont *boldFont = [font fontWithBoldTrait:YES andItalicTrait:NO]; + [bulletDictionary setObject:boldFont forKey:NSFontAttributeName]; + NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:nil]; + [bulletAttributedString setAttributes:bulletDictionary range:NSMakeRange(0, 1)]; + + // The tab after bullet should be similar to existing attributes + [bulletDictionary setObject:font forKey:NSFontAttributeName]; + [bulletAttributedString setAttributes:bulletDictionary range:NSMakeRange(1, 1)]; + [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; + CGSize expectedStringSize = [BULLET_STRING sizeWithFont:[dictionary objectForKey:NSFontAttributeName] constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT) lineBreakMode:NSLineBreakByWordWrapping]; @@ -364,8 +384,16 @@ - (void)richTextEditorToolbarDidSelectBulletList [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:range]; }]; - NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; - [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; + // If paragraph is empty move cursor to front of bullet, so the user can start typing right away + if (rangeOfParagraphsInSelectedText.count == 1 && rangeOfFirstParagraphRange.length == 0) + { + [self setSelectedRange:NSMakeRange(rangeOfFirstParagraphRange.location+2, 0)]; + } + else + { + NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; + [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; + } } #pragma mark - Private Methods - From 1200ada97b287620aebac6603a57ad8cad3aebfd Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 14:27:04 -0700 Subject: [PATCH 25/42] - Fixed comment for bullet list --- RichTextEditor/Source/RichTextEditorToolbar.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 93ae4d8..5a57bf6 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -381,7 +381,7 @@ - (void)populateToolbar lastAddedView = separatorView; } - // Text color + // Bullet List if (features & RichTextEditorFeatureBulletList || features & RichTextEditorFeatureAll) { [self addView:self.btnBulletList afterView:lastAddedView withSpacing:YES]; From 99fae4e21aad263d2926f2abbcc02fac8f39802a Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 14:36:09 -0700 Subject: [PATCH 26/42] - If initial selected range length is 0, after applying bullet place cursor at initial location. This way the user can apply a bullet on existing paragraph, and continue typing after the bullet is inserted --- RichTextEditor/Source/RichTextEditor.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 12f6210..90b3d8f 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -321,6 +321,7 @@ - (void)richTextEditorToolbarDidSelectTextAlignment:(NSTextAlignment)textAlignme - (void)richTextEditorToolbarDidSelectBulletList { + NSRange initialSelectedRange = self.selectedRange; NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; NSRange rangeOfFirstParagraphRange = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; BOOL firstParagraphHasBullet = ([[[self.attributedText string] substringFromIndex:rangeOfFirstParagraphRange.location] startsWithString:BULLET_STRING]) ? YES: NO; @@ -391,8 +392,15 @@ - (void)richTextEditorToolbarDidSelectBulletList } else { - NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; - [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; + if (initialSelectedRange.length == 0) + { + [self setSelectedRange:NSMakeRange(initialSelectedRange.location+rangeOffset, 0)]; + } + else + { + NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; + [self setSelectedRange:NSMakeRange(fullRange.location, fullRange.length+rangeOffset)]; + } } } From 665c81f27f17c5cf4c736a8f112841a425f6628b Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 15:46:13 -0700 Subject: [PATCH 27/42] - Resize toolbar and font button to display full font name - Keep scroll position of toolbar after redrawing content --- RichTextEditor/Source/RichTextEditorToolbar.m | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 5a57bf6..04d03e1 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -132,6 +132,8 @@ - (void)updateStateWithAttributes:(NSDictionary *)attributes NSNumber *existingStrikeThrough = [attributes objectForKey:NSStrikethroughStyleAttributeName]; self.btnStrikeThrough.on = (!existingStrikeThrough || existingStrikeThrough.intValue == NSUnderlineStyleNone) ? NO :YES; + + [self populateToolbar]; } #pragma mark - IBActions - @@ -231,6 +233,10 @@ - (void)textAlignmentSelected:(UIButton *)sender - (void)populateToolbar { + CGRect visibleRect; + visibleRect.origin = self.contentOffset; + visibleRect.size = self.bounds.size; + // Remove any existing subviews. for (UIView *subView in self.subviews) { @@ -250,6 +256,11 @@ - (void)populateToolbar if (features & RichTextEditorFeatureFont || features & RichTextEditorFeatureAll) { UIView *separatorView = [self separatorView]; + CGSize size = [self.btnFont sizeThatFits:CGSizeZero]; + CGRect rect = self.btnFont.frame; + rect.size.width = MAX(size.width + 25, 120); + self.btnFont.frame = rect; + [self addView:self.btnFont afterView:lastAddedView withSpacing:YES]; [self addView:separatorView afterView:self.btnFont withSpacing:YES]; lastAddedView = separatorView; @@ -387,6 +398,8 @@ - (void)populateToolbar [self addView:self.btnBulletList afterView:lastAddedView withSpacing:YES]; lastAddedView = self.btnBulletList; } + + [self scrollRectToVisible:visibleRect animated:NO]; } - (void)initializeButtons From b696dc7e964e954df396f65f4b562d1676586ab5 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Sep 2013 21:22:12 -0700 Subject: [PATCH 28/42] - Fixed crash when bullet is selected on empty text --- RichTextEditor.xcodeproj/project.pbxproj | 6 ++++++ .../Source/Categories/NSAttributedString+RichTextEditor.m | 3 +++ RichTextEditor/Source/RichTextEditor.m | 3 --- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/RichTextEditor.xcodeproj/project.pbxproj b/RichTextEditor.xcodeproj/project.pbxproj index 236b1e9..cfb63a2 100644 --- a/RichTextEditor.xcodeproj/project.pbxproj +++ b/RichTextEditor.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 15B6E20F17A4C2820015867C /* RichTextEditorToggleButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */; }; 15BFE3E917E4CFE700D89153 /* firstLineIndent.png in Resources */ = {isa = PBXBuildFile; fileRef = 15BFE3E817E4CFE700D89153 /* firstLineIndent.png */; }; 15BFE3EB17E4D01100D89153 /* firstLineIndent@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 15BFE3EA17E4D01100D89153 /* firstLineIndent@2x.png */; }; + 15BFE3F117EFF8B600D89153 /* NSString+RichTextEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */; }; 15C4131317A4444B0073D047 /* backcolor.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F217A4444B0073D047 /* backcolor.png */; }; 15C4131417A4444B0073D047 /* bold.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F317A4444B0073D047 /* bold.png */; }; 15C4131517A4444B0073D047 /* bullist.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F417A4444B0073D047 /* bullist.png */; }; @@ -188,6 +189,8 @@ 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RichTextEditorToggleButton.m; sourceTree = ""; }; 15BFE3E817E4CFE700D89153 /* firstLineIndent.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = firstLineIndent.png; sourceTree = ""; }; 15BFE3EA17E4D01100D89153 /* firstLineIndent@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "firstLineIndent@2x.png"; sourceTree = ""; }; + 15BFE3EF17EFF8B600D89153 /* NSString+RichTextEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RichTextEditor.h"; sourceTree = ""; }; + 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RichTextEditor.m"; sourceTree = ""; }; 15C412F217A4444B0073D047 /* backcolor.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = backcolor.png; sourceTree = ""; }; 15C412F317A4444B0073D047 /* bold.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bold.png; sourceTree = ""; }; 15C412F417A4444B0073D047 /* bullist.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bullist.png; sourceTree = ""; }; @@ -330,6 +333,8 @@ children = ( 153F955B179F9953005B2487 /* NSAttributedString+RichTextEditor.h */, 153F955C179F9953005B2487 /* NSAttributedString+RichTextEditor.m */, + 15BFE3EF17EFF8B600D89153 /* NSString+RichTextEditor.h */, + 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */, 153F955D179F9953005B2487 /* UIFont+RichTextEditor.h */, 153F955E179F9953005B2487 /* UIFont+RichTextEditor.m */, 153F955F179F9953005B2487 /* UIView+RichTextEditor.h */, @@ -647,6 +652,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 15BFE3F117EFF8B600D89153 /* NSString+RichTextEditor.m in Sources */, 153F959D179F9953005B2487 /* WEPopoverContainerView.m in Sources */, 153F95A4179F9953005B2487 /* RichTextEditorToolbar.m in Sources */, 153F959C179F9953005B2487 /* UIBarButtonItem+WEPopover.m in Sources */, diff --git a/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m index 15052e2..5868899 100644 --- a/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m @@ -33,6 +33,9 @@ @implementation NSAttributedString (RichTextEditor) - (NSRange)firstParagraphRangeFromTextRange:(NSRange)range { + if (self.string.length == 0) + return NSMakeRange(0, 0); + NSInteger start = -1; NSInteger end = -1; NSInteger length = 0; diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 90b3d8f..2e5d242 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -424,9 +424,6 @@ - (CGRect)frameOfTextAtRange:(NSRange)range - (void)enumarateThroughParagraphsInRange:(NSRange)range withBlock:(void (^)(NSRange paragraphRange))block { - if (![self hasText]) - return; - NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; for (int i=0 ; i Date: Wed, 25 Sep 2013 18:00:21 -0700 Subject: [PATCH 29/42] - Removed the logic that prevents existing typing attributes to be populated on toolbar during initial load - Added logic to insert a bullet when entering a new line - Added a logic to delete bullet (both characters) when back space is selected in front of a bullet --- RichTextEditor/Source/RichTextEditor.m | 45 ++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 2e5d242..18f731a 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -91,9 +91,16 @@ - (void)commonInitialization [self setupMenuItems]; - //If there is text already, then we do want to update the toolbar. Otherwise we don't. - if ([self hasText]) - [self updateToolbarState]; + [self updateToolbarState]; + + // When text changes check to see if we need to add bullet, or delete bullet on backspace + [[NSNotificationCenter defaultCenter] addObserverForName:UITextViewTextDidChangeNotification + object:self + queue:nil + usingBlock:^(NSNotification *n){ + [self applyBulletListIfApplicable]; + [self deleteBulletListWhenApplicable]; + }]; } #pragma mark - Override Methods - @@ -165,9 +172,6 @@ - (void)setupMenuItems - (void)selectParagraph:(id)sender { - if (![self hasText]) - return; - NSRange range = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; [self setSelectedRange:range]; @@ -619,6 +623,35 @@ - (CGRect)currentScreenBoundsDependOnOrientation return screenBounds ; } +- (void)applyBulletListIfApplicable +{ + NSRange rangeOfCurrentParagraph = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; + if (rangeOfCurrentParagraph.length != 0) + return; + + NSRange rangeOfPreviousParagraph = [self.attributedText firstParagraphRangeFromTextRange:NSMakeRange(rangeOfCurrentParagraph.location-1, 0)]; + if ([[self.attributedText.string substringFromIndex:rangeOfPreviousParagraph.location] startsWithString:BULLET_STRING]) + [self richTextEditorToolbarDidSelectBulletList]; +} + +- (void)deleteBulletListWhenApplicable +{ + NSRange range = self.selectedRange; + + if (range.location > 0) + { + NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; + + if ([[[attributedString.string substringFromIndex:range.location-1] substringToIndex:1] isEqual:@"•"]) + { + [attributedString deleteCharactersInRange:NSMakeRange(range.location-1, 1)]; + self.attributedText = attributedString; + [self setSelectedRange:NSMakeRange(range.location-1, 0)]; + } + + } +} + #pragma mark - RichTextEditorToolbarDataSource Methods - - (NSArray *)fontFamilySelectionForRichTextEditorToolbar From b1ff88eba82e1bed87da306781baee32a2ce0c6b Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Wed, 25 Sep 2013 18:23:46 -0700 Subject: [PATCH 30/42] - Fixed a memory leak --- RichTextEditor/Source/Categories/UIFont+RichTextEditor.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m index fd21865..7b034ad 100644 --- a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m @@ -58,6 +58,9 @@ + (UIFont *)fontWithName:(NSString *)name size:(CGFloat)size boldTrait:(BOOL)isB newFontRef = CTFontCreateCopyWithSymbolicTraits(fontWithoutTrait, 0.0, NULL, traits, traits); } + if (fontWithoutTrait) + CFRelease(fontWithoutTrait); + if (newFontRef) { NSString *fontNameKey = (__bridge NSString *)(CTFontCopyName(newFontRef, kCTFontPostScriptNameKey)); From 7b5d8087e1e6b174e8b2fc2db7a32af972d697b9 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Wed, 25 Sep 2013 18:35:20 -0700 Subject: [PATCH 31/42] - Performance improvement (Don't make a copy of attributed string unless there is a bullet and the mutable copy is needed) --- RichTextEditor/Source/RichTextEditor.m | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 18f731a..8f7b5dd 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -640,15 +640,13 @@ - (void)deleteBulletListWhenApplicable if (range.location > 0) { - NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; - - if ([[[attributedString.string substringFromIndex:range.location-1] substringToIndex:1] isEqual:@"•"]) + if ([[[self.attributedText.string substringFromIndex:range.location-1] substringToIndex:1] isEqual:@"•"]) { - [attributedString deleteCharactersInRange:NSMakeRange(range.location-1, 1)]; - self.attributedText = attributedString; + NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; + [mutableAttributedString deleteCharactersInRange:NSMakeRange(range.location-1, 1)]; + self.attributedText = mutableAttributedString; [self setSelectedRange:NSMakeRange(range.location-1, 0)]; } - } } From 50f6f6e627990c13afb97716cb89b363eae3ede7 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 29 Sep 2013 19:45:45 -0700 Subject: [PATCH 32/42] - Workaround for the bug where in iOS6 accessing typingAttribute on an empty text causes a crash - Added a method to pass html string and populate the rich text editor (Only works on iOS7 +) - Making bullet list consistent with the way iOS parses html to bullet attributed string --- RichTextEditor.xcodeproj/project.pbxproj | 6 -- .../Categories/NSString+RichTextEditor.h | 34 ------- .../Categories/NSString+RichTextEditor.m | 40 --------- RichTextEditor/Source/RichTextEditor.h | 1 + RichTextEditor/Source/RichTextEditor.m | 90 ++++++++++++------- 5 files changed, 60 insertions(+), 111 deletions(-) delete mode 100644 RichTextEditor/Source/Categories/NSString+RichTextEditor.h delete mode 100644 RichTextEditor/Source/Categories/NSString+RichTextEditor.m diff --git a/RichTextEditor.xcodeproj/project.pbxproj b/RichTextEditor.xcodeproj/project.pbxproj index cfb63a2..236b1e9 100644 --- a/RichTextEditor.xcodeproj/project.pbxproj +++ b/RichTextEditor.xcodeproj/project.pbxproj @@ -60,7 +60,6 @@ 15B6E20F17A4C2820015867C /* RichTextEditorToggleButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */; }; 15BFE3E917E4CFE700D89153 /* firstLineIndent.png in Resources */ = {isa = PBXBuildFile; fileRef = 15BFE3E817E4CFE700D89153 /* firstLineIndent.png */; }; 15BFE3EB17E4D01100D89153 /* firstLineIndent@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 15BFE3EA17E4D01100D89153 /* firstLineIndent@2x.png */; }; - 15BFE3F117EFF8B600D89153 /* NSString+RichTextEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */; }; 15C4131317A4444B0073D047 /* backcolor.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F217A4444B0073D047 /* backcolor.png */; }; 15C4131417A4444B0073D047 /* bold.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F317A4444B0073D047 /* bold.png */; }; 15C4131517A4444B0073D047 /* bullist.png in Resources */ = {isa = PBXBuildFile; fileRef = 15C412F417A4444B0073D047 /* bullist.png */; }; @@ -189,8 +188,6 @@ 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RichTextEditorToggleButton.m; sourceTree = ""; }; 15BFE3E817E4CFE700D89153 /* firstLineIndent.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = firstLineIndent.png; sourceTree = ""; }; 15BFE3EA17E4D01100D89153 /* firstLineIndent@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "firstLineIndent@2x.png"; sourceTree = ""; }; - 15BFE3EF17EFF8B600D89153 /* NSString+RichTextEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RichTextEditor.h"; sourceTree = ""; }; - 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RichTextEditor.m"; sourceTree = ""; }; 15C412F217A4444B0073D047 /* backcolor.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = backcolor.png; sourceTree = ""; }; 15C412F317A4444B0073D047 /* bold.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bold.png; sourceTree = ""; }; 15C412F417A4444B0073D047 /* bullist.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bullist.png; sourceTree = ""; }; @@ -333,8 +330,6 @@ children = ( 153F955B179F9953005B2487 /* NSAttributedString+RichTextEditor.h */, 153F955C179F9953005B2487 /* NSAttributedString+RichTextEditor.m */, - 15BFE3EF17EFF8B600D89153 /* NSString+RichTextEditor.h */, - 15BFE3F017EFF8B600D89153 /* NSString+RichTextEditor.m */, 153F955D179F9953005B2487 /* UIFont+RichTextEditor.h */, 153F955E179F9953005B2487 /* UIFont+RichTextEditor.m */, 153F955F179F9953005B2487 /* UIView+RichTextEditor.h */, @@ -652,7 +647,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 15BFE3F117EFF8B600D89153 /* NSString+RichTextEditor.m in Sources */, 153F959D179F9953005B2487 /* WEPopoverContainerView.m in Sources */, 153F95A4179F9953005B2487 /* RichTextEditorToolbar.m in Sources */, 153F959C179F9953005B2487 /* UIBarButtonItem+WEPopover.m in Sources */, diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h b/RichTextEditor/Source/Categories/NSString+RichTextEditor.h deleted file mode 100644 index 2667f4c..0000000 --- a/RichTextEditor/Source/Categories/NSString+RichTextEditor.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// NSString+RichTextEditor.h -// RichTextEditor -// -// Created by Aryan Gh on 9/15/13. -// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. -// -// https://github.com/aryaxt/iOS-Rich-Text-Editor -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import - -@interface NSString (RichTextEditor) - -- (BOOL)startsWithString:(NSString *)string; - -@end diff --git a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSString+RichTextEditor.m deleted file mode 100644 index 2aa80c2..0000000 --- a/RichTextEditor/Source/Categories/NSString+RichTextEditor.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// NSString+RichTextEditor.m -// RichTextEditor -// -// Created by Aryan Gh on 9/15/13. -// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. -// -// https://github.com/aryaxt/iOS-Rich-Text-Editor -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import "NSString+RichTextEditor.h" - -@implementation NSString (RichTextEditor) - -- (BOOL)startsWithString:(NSString *)string -{ - if (!string.length || string.length > self.length) - return NO; - - return ([[self substringToIndex:string.length] isEqualToString:string]) ? YES : NO; -} - -@end diff --git a/RichTextEditor/Source/RichTextEditor.h b/RichTextEditor/Source/RichTextEditor.h index 80e1081..b23f4d3 100644 --- a/RichTextEditor/Source/RichTextEditor.h +++ b/RichTextEditor/Source/RichTextEditor.h @@ -49,5 +49,6 @@ - (void)setBorderColor:(UIColor*)borderColor; - (void)setBorderWidth:(CGFloat)borderWidth; - (NSString *)htmlString; +- (void)setHtmlString:(NSString *)htmlString; @end diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 8f7b5dd..2f0ea34 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -29,11 +29,11 @@ #import #import "UIFont+RichTextEditor.h" #import "NSAttributedString+RichTextEditor.h" -#import "NSString+RichTextEditor.h" #import "UIView+RichTextEditor.h" +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) #define RICHTEXTEDITOR_TOOLBAR_HEIGHT 40 -#define BULLET_STRING @"•\t" +#define BULLET_STRING @"\t•\t" @interface RichTextEditor() @property (nonatomic, strong) RichTextEditorToolbar *toolBar; @@ -90,7 +90,6 @@ - (void)commonInitialization self.defaultIndentationSize = 15; [self setupMenuItems]; - [self updateToolbarState]; // When text changes check to see if we need to add bullet, or delete bullet on backspace @@ -181,9 +180,42 @@ - (void)selectParagraph:(id)sender #pragma mark - Public Methods - +- (void)setHtmlString:(NSString *)htmlString +{ + if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) + { + NSLog(@"Method setHtmlString is only supported on iOS 7 and above"); + return; + } + + NSError *error ; + NSData *data = [htmlString dataUsingEncoding:NSUTF8StringEncoding]; + NSAttributedString *str = [[NSAttributedString alloc] initWithData:data + + options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, + NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} + documentAttributes:nil error:&error]; + + if (error) + NSLog(@"%@", error); + else + self.attributedText = str; +} + - (NSString *)htmlString { - return [self.attributedText htmlString]; + if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) + { + NSLog(@"Method setHtmlString is only supported on iOS 7 and above"); + return nil; + } + + NSData *data = [self.attributedText dataFromRange:NSMakeRange(0, self.text.length) documentAttributes:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, + NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} + error:nil]; + + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + //return [self.attributedText htmlString]; } - (void)setBorderColor:(UIColor *)borderColor @@ -328,50 +360,42 @@ - (void)richTextEditorToolbarDidSelectBulletList NSRange initialSelectedRange = self.selectedRange; NSArray *rangeOfParagraphsInSelectedText = [self.attributedText rangeOfParagraphsFromTextRange:self.selectedRange]; NSRange rangeOfFirstParagraphRange = [self.attributedText firstParagraphRangeFromTextRange:self.selectedRange]; - BOOL firstParagraphHasBullet = ([[[self.attributedText string] substringFromIndex:rangeOfFirstParagraphRange.location] startsWithString:BULLET_STRING]) ? YES: NO; + BOOL firstParagraphHasBullet = ([[[self.attributedText string] substringFromIndex:rangeOfFirstParagraphRange.location] hasPrefix:BULLET_STRING]) ? YES: NO; __block NSInteger rangeOffset = 0; [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ NSRange range = NSMakeRange(paragraphRange.location + rangeOffset, paragraphRange.length); NSMutableAttributedString *currentAttributedString = [self.attributedText mutableCopy]; - NSDictionary *dictionary = [self dictionaryAtIndex:range.location]; + NSDictionary *dictionary = [self dictionaryAtIndex:MAX((int)range.location-1, 0)]; NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - BOOL currentParagraphHasBullet = ([[[currentAttributedString string] substringFromIndex:range.location] startsWithString:BULLET_STRING]) ? YES : NO; + BOOL currentParagraphHasBullet = ([[[currentAttributedString string] substringFromIndex:range.location] hasPrefix:BULLET_STRING]) ? YES : NO; if (firstParagraphHasBullet != currentParagraphHasBullet) return; if (currentParagraphHasBullet) { - range = NSMakeRange(range.location, range.length-2); + range = NSMakeRange(range.location, range.length - BULLET_STRING.length); - [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, 2)]; + [currentAttributedString deleteCharactersInRange:NSMakeRange(range.location, BULLET_STRING.length)]; paragraphStyle.firstLineHeadIndent = 0; paragraphStyle.headIndent = 0; - rangeOffset = rangeOffset - 2; + rangeOffset = rangeOffset - BULLET_STRING.length; } else { - range = NSMakeRange(range.location, range.length+2); + range = NSMakeRange(range.location, range.length + BULLET_STRING.length); // The bullet should be bold - NSMutableDictionary *bulletDictionary = [dictionary mutableCopy]; - UIFont *font = [bulletDictionary objectForKey:NSFontAttributeName]; - UIFont *boldFont = [font fontWithBoldTrait:YES andItalicTrait:NO]; - [bulletDictionary setObject:boldFont forKey:NSFontAttributeName]; NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:BULLET_STRING attributes:nil]; - [bulletAttributedString setAttributes:bulletDictionary range:NSMakeRange(0, 1)]; - - // The tab after bullet should be similar to existing attributes - [bulletDictionary setObject:font forKey:NSFontAttributeName]; - [bulletAttributedString setAttributes:bulletDictionary range:NSMakeRange(1, 1)]; + [bulletAttributedString setAttributes:dictionary range:NSMakeRange(0, BULLET_STRING.length)]; [currentAttributedString insertAttributedString:bulletAttributedString atIndex:range.location]; @@ -382,7 +406,7 @@ - (void)richTextEditorToolbarDidSelectBulletList paragraphStyle.firstLineHeadIndent = 0; paragraphStyle.headIndent = expectedStringSize.width; - rangeOffset = rangeOffset + 2; + rangeOffset = rangeOffset + BULLET_STRING.length; } self.attributedText = currentAttributedString; @@ -392,7 +416,7 @@ - (void)richTextEditorToolbarDidSelectBulletList // If paragraph is empty move cursor to front of bullet, so the user can start typing right away if (rangeOfParagraphsInSelectedText.count == 1 && rangeOfFirstParagraphRange.length == 0) { - [self setSelectedRange:NSMakeRange(rangeOfFirstParagraphRange.location+2, 0)]; + [self setSelectedRange:NSMakeRange(rangeOfFirstParagraphRange.location + BULLET_STRING.length, 0)]; } else { @@ -443,6 +467,10 @@ - (void)enumarateThroughParagraphsInRange:(NSRange)range withBlock:(void (^)(NSR - (void)updateToolbarState { + // There is a bug in iOS6 that causes a crash when accessing typingAttribute on an empty text + if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0") && ![self hasText]) + return; + // If no text exists or typing attributes is in progress update toolbar using typing attributes instead of selected text if (self.typingAttributesInProgress || ![self hasText]) { @@ -450,12 +478,12 @@ - (void)updateToolbarState } else { - int location = [self offsetFromPosition:self.beginningOfDocument toPosition:self.selectedTextRange.start]; - - if (location == self.text.length) - location --; + NSInteger location = (self.selectedRange.length == 0) + ? MAX((int)self.selectedRange.location-1, 0) + : (int)self.selectedRange.location; - [self.toolBar updateStateWithAttributes:[self.attributedText attributesAtIndex:location effectiveRange:nil]]; + NSDictionary *attributes = [self.attributedText attributesAtIndex:location effectiveRange:nil]; + [self.toolBar updateStateWithAttributes:attributes]; } } @@ -630,7 +658,7 @@ - (void)applyBulletListIfApplicable return; NSRange rangeOfPreviousParagraph = [self.attributedText firstParagraphRangeFromTextRange:NSMakeRange(rangeOfCurrentParagraph.location-1, 0)]; - if ([[self.attributedText.string substringFromIndex:rangeOfPreviousParagraph.location] startsWithString:BULLET_STRING]) + if ([[self.attributedText.string substringFromIndex:rangeOfPreviousParagraph.location] hasPrefix:BULLET_STRING]) [self richTextEditorToolbarDidSelectBulletList]; } @@ -640,12 +668,12 @@ - (void)deleteBulletListWhenApplicable if (range.location > 0) { - if ([[[self.attributedText.string substringFromIndex:range.location-1] substringToIndex:1] isEqual:@"•"]) + if ((int)range.location-2 >= 0 && [[self.attributedText.string substringFromIndex:range.location-2] hasPrefix:@"\t•"]) { NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; - [mutableAttributedString deleteCharactersInRange:NSMakeRange(range.location-1, 1)]; + [mutableAttributedString deleteCharactersInRange:NSMakeRange(range.location-2, 2)]; self.attributedText = mutableAttributedString; - [self setSelectedRange:NSMakeRange(range.location-1, 0)]; + [self setSelectedRange:NSMakeRange(range.location-2, 0)]; } } } From ad985cbc1044a3907bfa475fbce48beaa01a211a Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 29 Sep 2013 21:04:12 -0700 Subject: [PATCH 33/42] - Added TextAttachment functionality (Inserting images into RichTextEditor) --- RichTextEditor/Source/RichTextEditor.m | 12 +++++- RichTextEditor/Source/RichTextEditorToolbar.h | 6 ++- RichTextEditor/Source/RichTextEditorToolbar.m | 41 ++++++++++++++++++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 2f0ea34..c66fb66 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -31,7 +31,6 @@ #import "NSAttributedString+RichTextEditor.h" #import "UIView+RichTextEditor.h" -#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) #define RICHTEXTEDITOR_TOOLBAR_HEIGHT 40 #define BULLET_STRING @"\t•\t" @@ -432,6 +431,17 @@ - (void)richTextEditorToolbarDidSelectBulletList } } +- (void)richTextEditorToolbarDidSelectTextAttachment:(UIImage *)textAttachment +{ + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + [attachment setImage:textAttachment]; + NSAttributedString *attributedStringAttachment = [NSAttributedString attributedStringWithAttachment:attachment]; + + NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; + [attributedString insertAttributedString:attributedStringAttachment atIndex:self.selectedRange.location]; + self.attributedText = attributedString; +} + #pragma mark - Private Methods - - (CGRect)frameOfTextAtRange:(NSRange)range diff --git a/RichTextEditor/Source/RichTextEditorToolbar.h b/RichTextEditor/Source/RichTextEditorToolbar.h index 7ecc845..ac69dc8 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.h +++ b/RichTextEditor/Source/RichTextEditorToolbar.h @@ -27,6 +27,8 @@ #import +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) + typedef enum{ RichTextEditorToolbarPresentationStyleModal, RichTextEditorToolbarPresentationStylePopover @@ -54,7 +56,8 @@ typedef enum{ RichTextEditorFeatureParagraphIndentation = 1 << 12, RichTextEditorFeatureParagraphFirstLineIndentation = 1 << 13, RichTextEditorFeatureBulletList = 1 << 14, - RichTextEditorFeatureAll = 1 << 15 + RichTextEditorTextAttachment = 1 << 15, + RichTextEditorFeatureAll = 1 << 16 }RichTextEditorFeature; @protocol RichTextEditorToolbarDelegate @@ -63,6 +66,7 @@ typedef enum{ - (void)richTextEditorToolbarDidSelectUnderline; - (void)richTextEditorToolbarDidSelectStrikeThrough; - (void)richTextEditorToolbarDidSelectBulletList; +- (void)richTextEditorToolbarDidSelectTextAttachment:(UIImage *)textAttachment; - (void)richTextEditorToolbarDidSelectParagraphFirstLineHeadIndent; - (void)richTextEditorToolbarDidSelectParagraphIndentation:(ParagraphIndentation)paragraphIndentation; - (void)richTextEditorToolbarDidSelectFontSize:(NSNumber *)fontSize; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 04d03e1..4a0d024 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -39,7 +39,7 @@ #define ITEM_TOP_AND_BOTTOM_BORDER 5 #define ITEM_WITH 40 -@interface RichTextEditorToolbar() +@interface RichTextEditorToolbar() @property (nonatomic, strong) id popover; @property (nonatomic, strong) RichTextEditorToggleButton *btnBold; @property (nonatomic, strong) RichTextEditorToggleButton *btnItalic; @@ -57,6 +57,7 @@ @interface RichTextEditorToolbar() Date: Sun, 3 Nov 2013 18:19:55 -0800 Subject: [PATCH 34/42] Fixed #32 Inserting an image loses the attributed and won't apply to text being typed after the image --- RichTextEditor/Source/RichTextEditor.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index c66fb66..80b3890 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -437,8 +437,11 @@ - (void)richTextEditorToolbarDidSelectTextAttachment:(UIImage *)textAttachment [attachment setImage:textAttachment]; NSAttributedString *attributedStringAttachment = [NSAttributedString attributedStringWithAttachment:attachment]; + NSDictionary *previousAttributes = [self dictionaryAtIndex:self.selectedRange.location]; + NSMutableAttributedString *attributedString = [self.attributedText mutableCopy]; [attributedString insertAttributedString:attributedStringAttachment atIndex:self.selectedRange.location]; + [attributedString addAttributes:previousAttributes range:NSMakeRange(self.selectedRange.location, 1)]; self.attributedText = attributedString; } From 07fe5b0914f0360e50712c9d826cba0c412b5ae6 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Mon, 11 Nov 2013 18:29:57 -0800 Subject: [PATCH 35/42] - Overriding setAttributedText & setText and updating toolbar state to make sure that when the text is changed through code the toolbar gets updated --- RichTextEditor/Source/RichTextEditor.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 80b3890..18de0f1 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -155,6 +155,18 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender return [super canPerformAction:action withSender:sender]; } +- (void)setAttributedText:(NSAttributedString *)attributedText +{ + [super setAttributedText:attributedText]; + [self updateToolbarState]; +} + +- (void)setText:(NSString *)text +{ + [super setText:text]; + [self updateToolbarState]; +} + #pragma mark - MenuController Methods - - (void)setupMenuItems From 99d13c61c3937f1be10c336c45813454b033467f Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Mon, 11 Nov 2013 18:38:19 -0800 Subject: [PATCH 36/42] - We want to update the toolbar when the font changes as well --- RichTextEditor/Source/RichTextEditor.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 18de0f1..c7810ef 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -167,6 +167,12 @@ - (void)setText:(NSString *)text [self updateToolbarState]; } +- (void)setFont:(UIFont *)font +{ + [super setFont:font]; + [self updateToolbarState]; +} + #pragma mark - MenuController Methods - - (void)setupMenuItems From 57f921ec0da90d59528e5c50caa732bcd145cb61 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sun, 22 Dec 2013 10:05:39 -0800 Subject: [PATCH 37/42] - Fixed the problem with the color picker where selecting outside of the color picker causes the color to become black. - Sliding finger to top of the color picker will now select white, and sliding to the bottom will now select black. - Done button was renamed to select - There is now a clear button to completely remove color attributed when needed --- .../contents.xcworkspacedata | 7 ++ .../xcshareddata/RichTextEditor.xccheckout | 41 ++++++++ .../UserInterfaceState.xcuserstate | Bin 0 -> 69696 bytes .../WorkspaceSettings.xcsettings | 10 ++ .../xcdebugger/Breakpoints.xcbkptlist | 5 + .../xcdebugger/Breakpoints_v2.xcbkptlist | 23 +++++ .../xcschemes/RichTextEditor.xcscheme | 96 ++++++++++++++++++ .../xcschemes/xcschememanagement.plist | 27 +++++ RichTextEditor/Source/Assets/colorsOld.jpg | Bin 0 -> 30452 bytes RichTextEditor/Source/RichTextEditor.m | 26 ++++- .../RichTextEditorColorPickerViewController.m | 38 ++++--- .../RichTextEditorLinkPickerViewController.h | 27 +++++ .../RichTextEditorLinkPickerViewController.m | 82 +++++++++++++++ RichTextEditor/Source/RichTextEditorToolbar.m | 2 +- 14 files changed, 368 insertions(+), 16 deletions(-) create mode 100644 RichTextEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 RichTextEditor.xcodeproj/project.xcworkspace/xcshareddata/RichTextEditor.xccheckout create mode 100644 RichTextEditor.xcodeproj/project.xcworkspace/xcuserdata/aryaxt.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 RichTextEditor.xcodeproj/project.xcworkspace/xcuserdata/aryaxt.xcuserdatad/WorkspaceSettings.xcsettings create mode 100644 RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist create mode 100644 RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcschemes/RichTextEditor.xcscheme create mode 100644 RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 RichTextEditor/Source/Assets/colorsOld.jpg create mode 100644 RichTextEditor/Source/RichTextEditorLinkPickerViewController.h create mode 100644 RichTextEditor/Source/RichTextEditorLinkPickerViewController.m diff --git a/RichTextEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/RichTextEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..242f037 --- /dev/null +++ b/RichTextEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/RichTextEditor.xcodeproj/project.xcworkspace/xcshareddata/RichTextEditor.xccheckout b/RichTextEditor.xcodeproj/project.xcworkspace/xcshareddata/RichTextEditor.xccheckout new file mode 100644 index 0000000..f3b529d --- /dev/null +++ b/RichTextEditor.xcodeproj/project.xcworkspace/xcshareddata/RichTextEditor.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + 5094D3CF-B317-41F7-9AA1-801D09780AE3 + IDESourceControlProjectName + RichTextEditor + IDESourceControlProjectOriginsDictionary + + D9C29B00-DAC9-49D9-839A-1500CC1E9C1A + http://github.com/aryaxt/iOS-Rich-Text-Editor.git + + IDESourceControlProjectPath + RichTextEditor.xcodeproj/project.xcworkspace + IDESourceControlProjectRelativeInstallPathDictionary + + D9C29B00-DAC9-49D9-839A-1500CC1E9C1A + ../.. + + IDESourceControlProjectURL + http://github.com/aryaxt/iOS-Rich-Text-Editor.git + IDESourceControlProjectVersion + 110 + IDESourceControlProjectWCCIdentifier + D9C29B00-DAC9-49D9-839A-1500CC1E9C1A + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + D9C29B00-DAC9-49D9-839A-1500CC1E9C1A + IDESourceControlWCCName + iOS-Rich-Text-Editor + + + + diff --git a/RichTextEditor.xcodeproj/project.xcworkspace/xcuserdata/aryaxt.xcuserdatad/UserInterfaceState.xcuserstate b/RichTextEditor.xcodeproj/project.xcworkspace/xcuserdata/aryaxt.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..a5d5c62e5297d35e78a852fec11c83262b653f35 GIT binary patch literal 69696 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 + + + + HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges + + SnapshotAutomaticallyBeforeSignificantChanges + + + diff --git a/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist b/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist new file mode 100644 index 0000000..05301bc --- /dev/null +++ b/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist @@ -0,0 +1,5 @@ + + + diff --git a/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..1a2b502 --- /dev/null +++ b/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcschemes/RichTextEditor.xcscheme b/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcschemes/RichTextEditor.xcscheme new file mode 100644 index 0000000..3cf344b --- /dev/null +++ b/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcschemes/RichTextEditor.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcschemes/xcschememanagement.plist b/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..051c7c6 --- /dev/null +++ b/RichTextEditor.xcodeproj/xcuserdata/aryaxt.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + RichTextEditor.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 1583890D1735A5D7006DE713 + + primary + + + 158389371735A5D7006DE713 + + primary + + + + + diff --git a/RichTextEditor/Source/Assets/colorsOld.jpg b/RichTextEditor/Source/Assets/colorsOld.jpg new file mode 100644 index 0000000000000000000000000000000000000000..20e8f2ca7fabd85cd36237e1b8c168f8a2fd3035 GIT binary patch literal 30452 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 + +@protocol RichTextEditorLinkPickerViewControllerDelegate +- (void)richTextEditorLinkPickerViewControllerDidSelectLinkWithTitle:(NSString *)title andUrl:(NSURL *)url; +- (void)richTextEditorLinkPickerViewControllerDidSelectClose; +@end + +@protocol RichTextEditorLinkPickerViewControllerDataSource +- (BOOL)RichTextEditorLinkPickerViewControllerShouldDisplayToolbar; +@end + +@interface RichTextEditorLinkPickerViewController : UIViewController + +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id dataSource; +@property (nonatomic, strong) UITextField *txtTitle; +@property (nonatomic, strong) UITextField *txtUrl; + +@end diff --git a/RichTextEditor/Source/RichTextEditorLinkPickerViewController.m b/RichTextEditor/Source/RichTextEditorLinkPickerViewController.m new file mode 100644 index 0000000..0e1920d --- /dev/null +++ b/RichTextEditor/Source/RichTextEditorLinkPickerViewController.m @@ -0,0 +1,82 @@ +// +// RichTextEditorLinkPickerViewController.m +// RichTextEditor +// +// Created by Aryan Gh on 8/20/13. +// Copyright (c) 2013 Aryan Ghassemi. All rights reserved. +// + +#import "RichTextEditorLinkPickerViewController.h" + +@implementation RichTextEditorLinkPickerViewController + +#pragma mark - UIViewController Methods - + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor whiteColor]; + + UIButton *btnClose = [[UIButton alloc] initWithFrame:CGRectMake(5, 5, 60, 30)]; + [btnClose addTarget:self action:@selector(closeSelected:) forControlEvents:UIControlEventTouchUpInside]; + [btnClose.titleLabel setFont:[UIFont boldSystemFontOfSize:12]]; + [btnClose setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + [btnClose setTitle:@"Close" forState:UIControlStateNormal]; + [self.view addSubview:btnClose]; + + UIButton *btnDone = [[UIButton alloc] initWithFrame:CGRectMake(65, 5, 60, 30)]; + [btnDone addTarget:self action:@selector(doneSelected:) forControlEvents:UIControlEventTouchUpInside]; + [btnDone.titleLabel setFont:[UIFont boldSystemFontOfSize:12]]; + [btnDone setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + [btnDone setTitle:@"Done" forState:UIControlStateNormal]; + [self.view addSubview:btnDone]; + + self.txtTitle = [[UITextField alloc] initWithFrame:CGRectMake(5, 100, 300, 44)]; + [self.txtTitle setPlaceholder:@"Title"]; + [self.view addSubview:self.txtTitle]; + + self.txtUrl = [[UITextField alloc] initWithFrame:CGRectMake(5, 150, 300, 44)]; + [self.txtUrl setPlaceholder:@"Title"]; + [self.view addSubview:self.txtUrl]; + + if ([self.dataSource RichTextEditorLinkPickerViewControllerShouldDisplayToolbar]) + { + UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)]; + toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth; + [self.view addSubview:toolbar]; + + UIBarButtonItem *flexibleSpaceItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace + target:nil + action:nil]; + + UIBarButtonItem *doneItem = [[UIBarButtonItem alloc] initWithTitle:@"Done" + style:UIBarButtonItemStyleDone + target:self + action:@selector(doneSelected:)]; + + UIBarButtonItem *closeItem = [[UIBarButtonItem alloc] initWithTitle:@"Close" + style:UIBarButtonItemStyleDone + target:self + action:@selector(closeSelected:)]; + + [toolbar setItems:@[doneItem, flexibleSpaceItem , closeItem]]; + [self.view addSubview:toolbar]; + } + + self.contentSizeForViewInPopover = CGSizeMake(300, 240); +} + +#pragma mark - IBActions - + +- (void)closeSelected:(id)sender +{ + [self.delegate richTextEditorLinkPickerViewControllerDidSelectClose]; +} + +- (void)doneSelected:(id)sender +{ + [self.delegate richTextEditorLinkPickerViewControllerDidSelectLinkWithTitle:self.txtTitle.text andUrl:[NSURL URLWithString:self.txtUrl.text]]; +} + +@end diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 4a0d024..13631da 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -39,7 +39,7 @@ #define ITEM_TOP_AND_BOTTOM_BORDER 5 #define ITEM_WITH 40 -@interface RichTextEditorToolbar() +@interface RichTextEditorToolbar() @property (nonatomic, strong) id popover; @property (nonatomic, strong) RichTextEditorToggleButton *btnBold; @property (nonatomic, strong) RichTextEditorToggleButton *btnItalic; From f27b2ac16ca4739511a2202889dd2a9736f3bad8 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sat, 8 Feb 2014 09:07:57 -0800 Subject: [PATCH 38/42] - On iPhone (iOS7) when modal is presented, textView loses focus, when modal is dismissed textView doesn't become first responder automatically So there is now a new delegate that let's the RichTextEditor to become first responder after the modal is dismissed --- RichTextEditor/Source/RichTextEditor.m | 17 ++++++++++++++--- RichTextEditor/Source/RichTextEditorToolbar.h | 1 + RichTextEditor/Source/RichTextEditorToolbar.m | 6 ++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 1952dc8..864b01c 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -247,6 +247,12 @@ - (void)setBorderWidth:(CGFloat)borderWidth #pragma mark - RichTextEditorToolbarDelegate Methods - +- (void)richTextEditorToolbarDidDismissViewController +{ + if (![self isFirstResponder]) + [self becomeFirstResponder]; +} + - (void)richTextEditorToolbarDidSelectBold { UIFont *font = [self fontAtIndex:self.selectedRange.location]; @@ -515,9 +521,14 @@ - (void)updateToolbarState } else { - NSInteger location = (self.selectedRange.length == 0) - ? MAX((int)self.selectedRange.location-1, 0) - : (int)self.selectedRange.location; + NSInteger location = 0; + + if (self.selectedRange.location != NSNotFound) + { + location = (self.selectedRange.length == 0) + ? MAX((int)self.selectedRange.location-1, 0) + : (int)self.selectedRange.location; + } NSDictionary *attributes = [self.attributedText attributesAtIndex:location effectiveRange:nil]; [self.toolBar updateStateWithAttributes:attributes]; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.h b/RichTextEditor/Source/RichTextEditorToolbar.h index ac69dc8..d6a7145 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.h +++ b/RichTextEditor/Source/RichTextEditorToolbar.h @@ -61,6 +61,7 @@ typedef enum{ }RichTextEditorFeature; @protocol RichTextEditorToolbarDelegate +- (void)richTextEditorToolbarDidDismissViewController; - (void)richTextEditorToolbarDidSelectBold; - (void)richTextEditorToolbarDidSelectItalic; - (void)richTextEditorToolbarDidSelectUnderline; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 13631da..1552d63 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -595,6 +595,8 @@ - (void)dismissViewController { [self.popover dismissPopoverAnimated:YES]; } + + [self.delegate richTextEditorToolbarDidDismissViewController]; } #pragma mark - RichTextEditorColorPickerViewControllerDelegate & RichTextEditorColorPickerViewControllerDataSource Methods - @@ -675,12 +677,12 @@ - (void)imagePickerController:(UIImagePickerController *)picker didFinishPicking { UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; [self.delegate richTextEditorToolbarDidSelectTextAttachment:image]; - [self.popover dismissPopoverAnimated:YES]; + [self dismissViewController]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - [self.popover dismissPopoverAnimated:YES]; + [self dismissViewController]; } @end From a7ba0721fc7b9643ceb61c0b9f9bea855eb359e5 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sat, 8 Feb 2014 09:44:31 -0800 Subject: [PATCH 39/42] - Added 3 new datasource methods to allow the user to pass a custom colorPicker, fontPicker, or a fontSizePicker to richTextEditor in order to override the use of default pickers --- RichTextEditor.xcodeproj/project.pbxproj | 6 ++++ RichTextEditor/Source/RichTextEditor.h | 3 ++ RichTextEditor/Source/RichTextEditor.m | 24 ++++++++++++++ .../Source/RichTextEditorColorPicker.h | 31 +++++++++++++++++++ .../RichTextEditorColorPickerViewController.h | 17 ++-------- .../Source/RichTextEditorFontPicker.h | 26 ++++++++++++++++ .../RichTextEditorFontPickerViewController.h | 13 ++------ .../RichTextEditorFontPickerViewController.m | 5 ++- .../Source/RichTextEditorFontSizePicker.h | 26 ++++++++++++++++ ...chTextEditorFontSizePickerViewController.h | 13 ++------ RichTextEditor/Source/RichTextEditorToolbar.h | 6 ++++ RichTextEditor/Source/RichTextEditorToolbar.m | 25 ++++++++++++--- 12 files changed, 150 insertions(+), 45 deletions(-) create mode 100644 RichTextEditor/Source/RichTextEditorColorPicker.h create mode 100644 RichTextEditor/Source/RichTextEditorFontPicker.h create mode 100644 RichTextEditor/Source/RichTextEditorFontSizePicker.h diff --git a/RichTextEditor.xcodeproj/project.pbxproj b/RichTextEditor.xcodeproj/project.pbxproj index 236b1e9..1ebf1a3 100644 --- a/RichTextEditor.xcodeproj/project.pbxproj +++ b/RichTextEditor.xcodeproj/project.pbxproj @@ -216,6 +216,9 @@ 15C4133517A48C6D0073D047 /* buttoncenter.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buttoncenter.png; sourceTree = ""; }; 15C4133617A48C6D0073D047 /* buttonleft.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buttonleft.png; sourceTree = ""; }; 15C4133717A48C6D0073D047 /* buttonright.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buttonright.png; sourceTree = ""; }; + 15C9AC7818A69C69006E6F27 /* RichTextEditorColorPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RichTextEditorColorPicker.h; sourceTree = ""; }; + 15C9AC7918A69E12006E6F27 /* RichTextEditorFontPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RichTextEditorFontPicker.h; sourceTree = ""; }; + 15C9AC7A18A69E6E006E6F27 /* RichTextEditorFontSizePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RichTextEditorFontSizePicker.h; sourceTree = ""; }; 1AFAED6E17C7F9CA00E450B0 /* strikethrough@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "strikethrough@2x.png"; sourceTree = ""; }; 1AFAED6F17C7F9CA00E450B0 /* italic@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "italic@2x.png"; sourceTree = ""; }; 1AFAED7017C7F9CA00E450B0 /* underline@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "underline@2x.png"; sourceTree = ""; }; @@ -268,10 +271,13 @@ 153F9585179F9953005B2487 /* RichTextEditorToolbar.m */, 15B6E20D17A4C2820015867C /* RichTextEditorToggleButton.h */, 15B6E20E17A4C2820015867C /* RichTextEditorToggleButton.m */, + 15C9AC7818A69C69006E6F27 /* RichTextEditorColorPicker.h */, 153F957D179F9953005B2487 /* RichTextEditorColorPickerViewController.h */, 153F957E179F9953005B2487 /* RichTextEditorColorPickerViewController.m */, + 15C9AC7918A69E12006E6F27 /* RichTextEditorFontPicker.h */, 153F957F179F9953005B2487 /* RichTextEditorFontPickerViewController.h */, 153F9580179F9953005B2487 /* RichTextEditorFontPickerViewController.m */, + 15C9AC7A18A69E6E006E6F27 /* RichTextEditorFontSizePicker.h */, 153F9581179F9953005B2487 /* RichTextEditorFontSizePickerViewController.h */, 153F9582179F9953005B2487 /* RichTextEditorFontSizePickerViewController.m */, 153F9583179F9953005B2487 /* RichTextEditorPopover.h */, diff --git a/RichTextEditor/Source/RichTextEditor.h b/RichTextEditor/Source/RichTextEditor.h index b23f4d3..db84e3f 100644 --- a/RichTextEditor/Source/RichTextEditor.h +++ b/RichTextEditor/Source/RichTextEditor.h @@ -39,6 +39,9 @@ - (RichTextEditorFeature)featuresEnabledForRichTextEditor:(RichTextEditor *)richTextEditor; - (BOOL)shouldDisplayToolbarForRichTextEditor:(RichTextEditor *)richTextEditor; - (BOOL)shouldDisplayRichTextOptionsInMenuControllerForRichTextEditor:(RichTextEditor *)richTextEdiotor; +- (UIViewController *)colorPickerForRichTextEditor:(RichTextEditor *)richTextEdiotor withAction:(RichTextEditorColorPickerAction)action; +- (UIViewController *)fontPickerForRichTextEditor:(RichTextEditor *)richTextEdiotor; +- (UIViewController *)fontSizePickerForRichTextEditor:(RichTextEditor *)richTextEdiotor; @end @interface RichTextEditor : UITextView diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 864b01c..43f4534 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -475,6 +475,30 @@ - (void)richTextEditorToolbarDidSelectTextAttachment:(UIImage *)textAttachment self.attributedText = attributedString; } +- (UIViewController *)colorPickerForRichTextEditorToolbarWithAction:(RichTextEditorColorPickerAction)action +{ + if ([self.dataSource respondsToSelector:@selector(colorPickerForRichTextEditor:forAction:)]) + return [self.dataSource colorPickerForRichTextEditor:self withAction:action]; + + return nil; +} + +- (UIViewController *)fontPickerForRichTextEditorToolbar +{ + if ([self.dataSource respondsToSelector:@selector(fontPickerForRichTextEditor:)]) + return [self.dataSource fontPickerForRichTextEditor:self]; + + return nil; +} + +- (UIViewController *)fontSizePickerForRichTextEditorToolbar +{ + if ([self.dataSource respondsToSelector:@selector(fontSizePickerForRichTextEditor:)]) + return [self.dataSource fontSizePickerForRichTextEditor:self]; + + return nil; +} + #pragma mark - Private Methods - - (CGRect)frameOfTextAtRange:(NSRange)range diff --git a/RichTextEditor/Source/RichTextEditorColorPicker.h b/RichTextEditor/Source/RichTextEditorColorPicker.h new file mode 100644 index 0000000..6543efb --- /dev/null +++ b/RichTextEditor/Source/RichTextEditorColorPicker.h @@ -0,0 +1,31 @@ +// +// RichTextEditorColorPicker.h +// RichTextEditor +// +// Created by Aryan Gh on 2/8/14. +// Copyright (c) 2014 Aryan Ghassemi. All rights reserved. +// + +#import + +typedef enum { + RichTextEditorColorPickerActionTextForegroudColor, + RichTextEditorColorPickerActionTextBackgroundColor +}RichTextEditorColorPickerAction; + +@protocol RichTextEditorColorPickerViewControllerDelegate +- (void)richTextEditorColorPickerViewControllerDidSelectColor:(UIColor *)color withAction:(RichTextEditorColorPickerAction)action; +- (void)richTextEditorColorPickerViewControllerDidSelectClose; +@end + +@protocol RichTextEditorColorPickerViewControllerDataSource +- (BOOL)richTextEditorColorPickerViewControllerShouldDisplayToolbar; +@end + +@protocol RichTextEditorColorPicker + +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id dataSource; +@property (nonatomic, assign) RichTextEditorColorPickerAction action; + +@end diff --git a/RichTextEditor/Source/RichTextEditorColorPickerViewController.h b/RichTextEditor/Source/RichTextEditorColorPickerViewController.h index 0bdaeb5..b844139 100644 --- a/RichTextEditor/Source/RichTextEditorColorPickerViewController.h +++ b/RichTextEditor/Source/RichTextEditorColorPickerViewController.h @@ -28,22 +28,9 @@ #import #import #import "UIView+RichTextEditor.h" +#import "RichTextEditorColorPicker.h" -typedef enum { - RichTextEditorColorPickerActionTextForegroudColor, - RichTextEditorColorPickerActionTextBackgroundColor -}RichTextEditorColorPickerAction; - -@protocol RichTextEditorColorPickerViewControllerDelegate -- (void)richTextEditorColorPickerViewControllerDidSelectColor:(UIColor *)color withAction:(RichTextEditorColorPickerAction)action; -- (void)richTextEditorColorPickerViewControllerDidSelectClose; -@end - -@protocol RichTextEditorColorPickerViewControllerDataSource -- (BOOL)richTextEditorColorPickerViewControllerShouldDisplayToolbar; -@end - -@interface RichTextEditorColorPickerViewController : UIViewController +@interface RichTextEditorColorPickerViewController : UIViewController @property (nonatomic, weak) id delegate; @property (nonatomic, weak) id dataSource; diff --git a/RichTextEditor/Source/RichTextEditorFontPicker.h b/RichTextEditor/Source/RichTextEditorFontPicker.h new file mode 100644 index 0000000..c3262a6 --- /dev/null +++ b/RichTextEditor/Source/RichTextEditorFontPicker.h @@ -0,0 +1,26 @@ +// +// RichTextEditorFontPicker.h +// RichTextEditor +// +// Created by Aryan Gh on 2/8/14. +// Copyright (c) 2014 Aryan Ghassemi. All rights reserved. +// + +#import + +@protocol RichTextEditorFontPickerViewControllerDelegate +- (void)richTextEditorFontPickerViewControllerDidSelectFontWithName:(NSString *)fontName; +- (void)richTextEditorFontPickerViewControllerDidSelectClose; +@end + +@protocol RichTextEditorFontPickerViewControllerDataSource +- (NSArray *)richTextEditorFontPickerViewControllerCustomFontFamilyNamesForSelection; +- (BOOL)richTextEditorFontPickerViewControllerShouldDisplayToolbar; +@end + +@protocol RichTextEditorFontPicker + +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id dataSource; + +@end diff --git a/RichTextEditor/Source/RichTextEditorFontPickerViewController.h b/RichTextEditor/Source/RichTextEditorFontPickerViewController.h index 39734a3..6115435 100644 --- a/RichTextEditor/Source/RichTextEditorFontPickerViewController.h +++ b/RichTextEditor/Source/RichTextEditorFontPickerViewController.h @@ -26,18 +26,9 @@ // THE SOFTWARE. #import +#import "RichTextEditorFontPicker.h" -@protocol RichTextEditorFontPickerViewControllerDelegate -- (void)richTextEditorFontPickerViewControllerDidSelectFontWithName:(NSString *)fontName; -- (void)richTextEditorFontPickerViewControllerDidSelectClose; -@end - -@protocol RichTextEditorFontPickerViewControllerDataSource -- (NSArray *)richTextEditorFontPickerViewControllerCustomFontFamilyNamesForSelection; -- (BOOL)richTextEditorFontPickerViewControllerShouldDisplayToolbar; -@end - -@interface RichTextEditorFontPickerViewController : UIViewController +@interface RichTextEditorFontPickerViewController : UIViewController @property (nonatomic, weak) id delegate; @property (nonatomic, weak) id dataSource; diff --git a/RichTextEditor/Source/RichTextEditorFontPickerViewController.m b/RichTextEditor/Source/RichTextEditorFontPickerViewController.m index 4d40174..6be6412 100644 --- a/RichTextEditor/Source/RichTextEditorFontPickerViewController.m +++ b/RichTextEditor/Source/RichTextEditorFontPickerViewController.m @@ -35,11 +35,10 @@ - (void)viewDidLoad NSArray *customizedFontFamilies = [self.dataSource richTextEditorFontPickerViewControllerCustomFontFamilyNamesForSelection]; - if (customizedFontFamilies) { + if (customizedFontFamilies) self.fontNames = customizedFontFamilies; - } else { + else self.fontNames = [[UIFont familyNames] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; - } if ([self.dataSource richTextEditorFontPickerViewControllerShouldDisplayToolbar]) { diff --git a/RichTextEditor/Source/RichTextEditorFontSizePicker.h b/RichTextEditor/Source/RichTextEditorFontSizePicker.h new file mode 100644 index 0000000..f1917db --- /dev/null +++ b/RichTextEditor/Source/RichTextEditorFontSizePicker.h @@ -0,0 +1,26 @@ +// +// RichTextEditorFontSizePicker.h +// RichTextEditor +// +// Created by Aryan Gh on 2/8/14. +// Copyright (c) 2014 Aryan Ghassemi. All rights reserved. +// + +#import + +@protocol RichTextEditorFontSizePickerViewControllerDelegate +- (void)richTextEditorFontSizePickerViewControllerDidSelectFontSize:(NSNumber *)fontSize; +- (void)richTextEditorFontSizePickerViewControllerDidSelectClose; +@end + +@protocol RichTextEditorFontSizePickerViewControllerDataSource +- (BOOL)richTextEditorFontSizePickerViewControllerShouldDisplayToolbar; +- (NSArray *)richTextEditorFontSizePickerViewControllerCustomFontSizesForSelection; +@end + +@protocol RichTextEditorFontSizePicker + +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id dataSource; + +@end diff --git a/RichTextEditor/Source/RichTextEditorFontSizePickerViewController.h b/RichTextEditor/Source/RichTextEditorFontSizePickerViewController.h index 70d6341..8ba4001 100644 --- a/RichTextEditor/Source/RichTextEditorFontSizePickerViewController.h +++ b/RichTextEditor/Source/RichTextEditorFontSizePickerViewController.h @@ -26,18 +26,9 @@ // THE SOFTWARE. #import +#import "RichTextEditorFontSizePicker.h" -@protocol RichTextEditorFontSizePickerViewControllerDelegate -- (void)richTextEditorFontSizePickerViewControllerDidSelectFontSize:(NSNumber *)fontSize; -- (void)richTextEditorFontSizePickerViewControllerDidSelectClose; -@end - -@protocol RichTextEditorFontSizePickerViewControllerDataSource -- (BOOL)richTextEditorFontSizePickerViewControllerShouldDisplayToolbar; -- (NSArray *)richTextEditorFontSizePickerViewControllerCustomFontSizesForSelection; -@end - -@interface RichTextEditorFontSizePickerViewController : UIViewController +@interface RichTextEditorFontSizePickerViewController : UIViewController @property (nonatomic, weak) id delegate; @property (nonatomic, weak) id dataSource; diff --git a/RichTextEditor/Source/RichTextEditorToolbar.h b/RichTextEditor/Source/RichTextEditorToolbar.h index d6a7145..41b3b94 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.h +++ b/RichTextEditor/Source/RichTextEditorToolbar.h @@ -26,6 +26,9 @@ // THE SOFTWARE. #import +#import "RichTextEditorColorPicker.h" +#import "RichTextEditorFontPicker.h" +#import "RichTextEditorFontSizePicker.h" #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) @@ -85,6 +88,9 @@ typedef enum{ - (UIModalTransitionStyle)modalTransitionStyleForRichTextEditorToolbar; - (UIViewController *)firsAvailableViewControllerForRichTextEditorToolbar; - (RichTextEditorFeature)featuresEnabledForRichTextEditorToolbar; +- (UIViewController *)colorPickerForRichTextEditorToolbarWithAction:(RichTextEditorColorPickerAction)action; +- (UIViewController *)fontPickerForRichTextEditorToolbar; +- (UIViewController *)fontSizePickerForRichTextEditorToolbar; @end @interface RichTextEditorToolbar : UIScrollView diff --git a/RichTextEditor/Source/RichTextEditorToolbar.m b/RichTextEditor/Source/RichTextEditorToolbar.m index 1552d63..9143d88 100644 --- a/RichTextEditor/Source/RichTextEditorToolbar.m +++ b/RichTextEditor/Source/RichTextEditorToolbar.m @@ -181,7 +181,11 @@ - (void)paragraphHeadIndentOutdentSelected:(UIButton *)sender - (void)fontSizeSelected:(UIButton *)sender { - RichTextEditorFontSizePickerViewController *fontSizePicker = [[RichTextEditorFontSizePickerViewController alloc] init]; + UIViewController *fontSizePicker = [self.dataSource fontSizePickerForRichTextEditorToolbar]; + + if (!fontSizePicker) + fontSizePicker = [[RichTextEditorFontSizePickerViewController alloc] init]; + fontSizePicker.delegate = self; fontSizePicker.dataSource = self; [self presentViewController:fontSizePicker fromView:sender]; @@ -189,8 +193,11 @@ - (void)fontSizeSelected:(UIButton *)sender - (void)fontSelected:(UIButton *)sender { - RichTextEditorFontPickerViewController *fontPicker= [[RichTextEditorFontPickerViewController alloc] init]; - fontPicker.fontNames = [self.dataSource fontFamilySelectionForRichTextEditorToolbar]; + UIViewController *fontPicker = [self.dataSource fontPickerForRichTextEditorToolbar]; + + if (!fontPicker) + fontPicker= [[RichTextEditorFontPickerViewController alloc] init]; + fontPicker.delegate = self; fontPicker.dataSource = self; [self presentViewController:fontPicker fromView:sender]; @@ -198,7 +205,11 @@ - (void)fontSelected:(UIButton *)sender - (void)textBackgroundColorSelected:(UIButton *)sender { - RichTextEditorColorPickerViewController *colorPicker = [[RichTextEditorColorPickerViewController alloc] init]; + UIViewController *colorPicker = [self.dataSource colorPickerForRichTextEditorToolbarWithAction:RichTextEditorColorPickerActionTextBackgroundColor]; + + if (!colorPicker) + colorPicker = [[RichTextEditorColorPickerViewController alloc] init]; + colorPicker.action = RichTextEditorColorPickerActionTextBackgroundColor; colorPicker.delegate = self; colorPicker.dataSource = self; @@ -207,7 +218,11 @@ - (void)textBackgroundColorSelected:(UIButton *)sender - (void)textForegroundColorSelected:(UIButton *)sender { - RichTextEditorColorPickerViewController *colorPicker = [[RichTextEditorColorPickerViewController alloc] init]; + UIViewController *colorPicker = [self.dataSource colorPickerForRichTextEditorToolbarWithAction:RichTextEditorColorPickerActionTextForegroudColor]; + + if (!colorPicker) + colorPicker = [[RichTextEditorColorPickerViewController alloc] init]; + colorPicker.action = RichTextEditorColorPickerActionTextForegroudColor; colorPicker.delegate = self; colorPicker.dataSource = self; From a03f5adf04324e8444a48f6b0b28ffe1c0d0c5e1 Mon Sep 17 00:00:00 2001 From: Aryan Ghassemi Date: Sat, 8 Feb 2014 10:41:31 -0800 Subject: [PATCH 40/42] - When backspace is selected and bullet is removed, the paragraph head indent needs to be set to 0 so that new lines won't have bullet indent --- RichTextEditor/Source/RichTextEditor.m | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index 43f4534..f9ab1f0 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -758,10 +758,20 @@ - (void)deleteBulletListWhenApplicable { if ((int)range.location-2 >= 0 && [[self.attributedText.string substringFromIndex:range.location-2] hasPrefix:@"\t•"]) { + // Get rid of bullet string NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; [mutableAttributedString deleteCharactersInRange:NSMakeRange(range.location-2, 2)]; self.attributedText = mutableAttributedString; - [self setSelectedRange:NSMakeRange(range.location-2, 0)]; + NSRange newRange = NSMakeRange(range.location-2, 0); + [self setSelectedRange:newRange]; + + // Get rid of bullet indentation + NSRange rangeOfParagraph = [self.attributedText firstParagraphRangeFromTextRange:newRange]; + NSDictionary *dictionary = [self dictionaryAtIndex:newRange.location]; + NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; + paragraphStyle.firstLineHeadIndent = 0; + paragraphStyle.headIndent = 0; + [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:rangeOfParagraph]; } } } From 4ddd86bbd6764d0a052ffa2db4e90037562162d6 Mon Sep 17 00:00:00 2001 From: Aryan Date: Sat, 21 Jun 2014 20:30:00 -0700 Subject: [PATCH 41/42] Fixed some warnings Updated project settings --- RichTextEditor.xcodeproj/project.pbxproj | 20 +++++++++---------- .../NSAttributedString+RichTextEditor.m | 4 ++-- RichTextEditor/Source/RichTextEditor.m | 1 - RichTextEditorTests/RichTextEditorTests.h | 4 ++-- RichTextEditorTests/RichTextEditorTests.m | 2 +- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/RichTextEditor.xcodeproj/project.pbxproj b/RichTextEditor.xcodeproj/project.pbxproj index 1ebf1a3..b7442c5 100644 --- a/RichTextEditor.xcodeproj/project.pbxproj +++ b/RichTextEditor.xcodeproj/project.pbxproj @@ -46,7 +46,6 @@ 1583892C1735A5D7006DE713 /* MainStoryboard_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1583892A1735A5D7006DE713 /* MainStoryboard_iPhone.storyboard */; }; 1583892F1735A5D7006DE713 /* MainStoryboard_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1583892D1735A5D7006DE713 /* MainStoryboard_iPad.storyboard */; }; 158389321735A5D7006DE713 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 158389311735A5D7006DE713 /* ViewController.m */; }; - 1583893A1735A5D7006DE713 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 158389391735A5D7006DE713 /* SenTestingKit.framework */; }; 1583893B1735A5D7006DE713 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 158389121735A5D7006DE713 /* UIKit.framework */; }; 1583893C1735A5D7006DE713 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 158389141735A5D7006DE713 /* Foundation.framework */; }; 158389441735A5D7006DE713 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 158389421735A5D7006DE713 /* InfoPlist.strings */; }; @@ -172,8 +171,7 @@ 1583892E1735A5D7006DE713 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard_iPad.storyboard; sourceTree = ""; }; 158389301735A5D7006DE713 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 158389311735A5D7006DE713 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - 158389381735A5D7006DE713 /* RichTextEditorTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RichTextEditorTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; - 158389391735A5D7006DE713 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; + 158389381735A5D7006DE713 /* RichTextEditorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RichTextEditorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 158389411735A5D7006DE713 /* RichTextEditorTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RichTextEditorTests-Info.plist"; sourceTree = ""; }; 158389431735A5D7006DE713 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 158389451735A5D7006DE713 /* RichTextEditorTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RichTextEditorTests.h; sourceTree = ""; }; @@ -250,7 +248,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1583893A1735A5D7006DE713 /* SenTestingKit.framework in Frameworks */, 1583893B1735A5D7006DE713 /* UIKit.framework in Frameworks */, 1583893C1735A5D7006DE713 /* Foundation.framework in Frameworks */, ); @@ -397,7 +394,7 @@ isa = PBXGroup; children = ( 1583890E1735A5D7006DE713 /* RichTextEditor.app */, - 158389381735A5D7006DE713 /* RichTextEditorTests.octest */, + 158389381735A5D7006DE713 /* RichTextEditorTests.xctest */, ); name = Products; sourceTree = ""; @@ -409,7 +406,6 @@ 158389121735A5D7006DE713 /* UIKit.framework */, 158389141735A5D7006DE713 /* Foundation.framework */, 158389161735A5D7006DE713 /* CoreGraphics.framework */, - 158389391735A5D7006DE713 /* SenTestingKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -515,8 +511,8 @@ ); name = RichTextEditorTests; productName = RichTextEditorTests; - productReference = 158389381735A5D7006DE713 /* RichTextEditorTests.octest */; - productType = "com.apple.product-type.bundle"; + productReference = 158389381735A5D7006DE713 /* RichTextEditorTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -524,7 +520,8 @@ 158389051735A5D7006DE713 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0450; + LastTestingUpgradeCheck = 0510; + LastUpgradeCheck = 0510; ORGANIZATIONNAME = "Aryan Ghassemi"; }; buildConfigurationList = 158389081735A5D7006DE713 /* Build configuration list for PBXProject "RichTextEditor" */; @@ -749,6 +746,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.0; + ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -806,13 +804,13 @@ FRAMEWORK_SEARCH_PATHS = ( "\"$(SDKROOT)/Developer/Library/Frameworks\"", "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + "$(inherited)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "RichTextEditor/RichTextEditor-Prefix.pch"; INFOPLIST_FILE = "RichTextEditorTests/RichTextEditorTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = octest; }; name = Debug; }; @@ -823,13 +821,13 @@ FRAMEWORK_SEARCH_PATHS = ( "\"$(SDKROOT)/Developer/Library/Frameworks\"", "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + "$(inherited)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "RichTextEditor/RichTextEditor-Prefix.pch"; INFOPLIST_FILE = "RichTextEditorTests/RichTextEditorTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = octest; }; name = Release; }; diff --git a/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m b/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m index 5868899..5c55114 100644 --- a/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m @@ -44,7 +44,7 @@ - (NSRange)firstParagraphRangeFromTextRange:(NSRange)range range.location-1 : range.location; - for (int i=startingRange ; i>=0 ; i--) + for (NSInteger i=startingRange ; i>=0 ; i--) { char c = [self.string characterAtIndex:i]; if (c == '\n') @@ -58,7 +58,7 @@ - (NSRange)firstParagraphRangeFromTextRange:(NSRange)range NSInteger moveForwardIndex = (range.location > start) ? range.location : start; - for (int i=moveForwardIndex; i<= self.string.length-1 ; i++) + for (NSInteger i=moveForwardIndex; i<= self.string.length-1 ; i++) { char c = [self.string characterAtIndex:i]; if (c == '\n') diff --git a/RichTextEditor/Source/RichTextEditor.m b/RichTextEditor/Source/RichTextEditor.m index f9ab1f0..bd6b7e2 100644 --- a/RichTextEditor/Source/RichTextEditor.m +++ b/RichTextEditor/Source/RichTextEditor.m @@ -232,7 +232,6 @@ - (NSString *)htmlString error:nil]; return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - //return [self.attributedText htmlString]; } - (void)setBorderColor:(UIColor *)borderColor diff --git a/RichTextEditorTests/RichTextEditorTests.h b/RichTextEditorTests/RichTextEditorTests.h index fa927c9..0a14149 100644 --- a/RichTextEditorTests/RichTextEditorTests.h +++ b/RichTextEditorTests/RichTextEditorTests.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. // -#import +#import -@interface RichTextEditorTests : SenTestCase +@interface RichTextEditorTests : XCTestCase @end diff --git a/RichTextEditorTests/RichTextEditorTests.m b/RichTextEditorTests/RichTextEditorTests.m index a27c475..14041c8 100644 --- a/RichTextEditorTests/RichTextEditorTests.m +++ b/RichTextEditorTests/RichTextEditorTests.m @@ -26,7 +26,7 @@ - (void)tearDown - (void)testExample { - STFail(@"Unit tests are not implemented yet in RichTextEditorTests"); + XCTFail(@"Unit tests are not implemented yet in RichTextEditorTests"); } @end From 1158624c1a50c743b8d6a1450469476e01d30584 Mon Sep 17 00:00:00 2001 From: Caesar Wirth Date: Wed, 13 Aug 2014 19:34:14 +0900 Subject: [PATCH 42/42] Fix memory bug that was releasing font before using it --- RichTextEditor/Source/Categories/UIFont+RichTextEditor.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m index 7b034ad..62f831b 100644 --- a/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m +++ b/RichTextEditor/Source/Categories/UIFont+RichTextEditor.m @@ -64,8 +64,9 @@ + (UIFont *)fontWithName:(NSString *)name size:(CGFloat)size boldTrait:(BOOL)isB if (newFontRef) { NSString *fontNameKey = (__bridge NSString *)(CTFontCopyName(newFontRef, kCTFontPostScriptNameKey)); + CGFloat size = CTFontGetSize(newFontRef); CFRelease(newFontRef); - return [UIFont fontWithName:fontNameKey size:CTFontGetSize(newFontRef)]; + return [UIFont fontWithName:fontNameKey size:size]; } return nil;