CYRTextStorage.m 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. //
  2. // CYRTextStorage.m
  3. //
  4. // Version 0.2.0
  5. //
  6. // Created by Illya Busigin on 01/05/2014.
  7. // Copyright (c) 2014 Cyrillian, Inc.
  8. //
  9. // Distributed under MIT license.
  10. // Get the latest version from here:
  11. //
  12. // https://github.com/illyabusigin/CYRTextView
  13. //
  14. // The MIT License (MIT)
  15. //
  16. // Copyright (c) 2014 Cyrillian, Inc.
  17. //
  18. // Permission is hereby granted, free of charge, to any person obtaining a copy of
  19. // this software and associated documentation files (the "Software"), to deal in
  20. // the Software without restriction, including without limitation the rights to
  21. // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  22. // the Software, and to permit persons to whom the Software is furnished to do so,
  23. // subject to the following conditions:
  24. //
  25. // The above copyright notice and this permission notice shall be included in all
  26. // copies or substantial portions of the Software.
  27. //
  28. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  29. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  30. // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  31. // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  32. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  33. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  34. #import "CYRTextStorage.h"
  35. #import "CYRToken.h"
  36. @interface CYRTextStorage ()
  37. @property (nonatomic, strong) NSMutableAttributedString *attributedString;
  38. @property (nonatomic, strong) NSMutableDictionary *regularExpressionCache;
  39. @end
  40. @implementation CYRTextStorage
  41. #pragma mark - Initialization & Setup
  42. - (id)init
  43. {
  44. if (self = [super init])
  45. {
  46. _defaultFont = [UIFont systemFontOfSize:12.0f];
  47. _attributedString = [NSMutableAttributedString new];
  48. _tokens = @[];
  49. _regularExpressionCache = @{}.mutableCopy;
  50. }
  51. return self;
  52. }
  53. #pragma mark - Overrides
  54. - (void)setTokens:(NSMutableArray *)tokens
  55. {
  56. _tokens = tokens;
  57. // Clear the regular expression cache
  58. [self.regularExpressionCache removeAllObjects];
  59. // Redraw all text
  60. [self update];
  61. }
  62. - (NSString *)string
  63. {
  64. return [_attributedString string];
  65. }
  66. - (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
  67. {
  68. return [_attributedString attributesAtIndex:location effectiveRange:range];
  69. }
  70. - (void)replaceCharactersInRange:(NSRange)range withString:(NSString*)str
  71. {
  72. [self beginEditing];
  73. [_attributedString replaceCharactersInRange:range withString:str];
  74. [self edited:NSTextStorageEditedCharacters | NSTextStorageEditedAttributes range:range changeInLength:str.length - range.length];
  75. [self endEditing];
  76. }
  77. - (void)setAttributes:(NSDictionary*)attrs range:(NSRange)range
  78. {
  79. [self beginEditing];
  80. [_attributedString setAttributes:attrs range:range];
  81. [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
  82. [self endEditing];
  83. }
  84. -(void)processEditing
  85. {
  86. [self performReplacementsForRange:[self editedRange]];
  87. [super processEditing];
  88. }
  89. - (void)performReplacementsForRange:(NSRange)changedRange
  90. {
  91. NSRange extendedRange = NSUnionRange(changedRange, [[_attributedString string] lineRangeForRange:NSMakeRange(NSMaxRange(changedRange), 0)]);
  92. [self applyStylesToRange:extendedRange];
  93. }
  94. -(void)update
  95. {
  96. [self addAttributes:@{NSFontAttributeName : self.defaultFont} range:NSMakeRange(0, self.length)];
  97. [self applyStylesToRange:NSMakeRange(0, self.length)];
  98. }
  99. - (void)applyStylesToRange:(NSRange)searchRange
  100. {
  101. if (self.editedRange.location == NSNotFound)
  102. {
  103. return;
  104. }
  105. NSRange paragaphRange = [self.string paragraphRangeForRange: self.editedRange];
  106. // Reset the text attributes
  107. [self setAttributes:@{NSForegroundColorAttributeName : [UIColor blackColor]} range:paragaphRange];
  108. [self setAttributes:@{NSFontAttributeName : self.defaultFont} range:paragaphRange];
  109. for (CYRToken *attribute in self.tokens)
  110. {
  111. NSRegularExpression *regex = [self expressionForDefinition:attribute.name];
  112. [regex enumerateMatchesInString:self.string options:0 range:paragaphRange
  113. usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  114. [attribute.attributes enumerateKeysAndObjectsUsingBlock:^(NSString *attributeName, id attributeValue, BOOL *stop) {
  115. [self addAttribute:attributeName value:attributeValue range:result.range];
  116. }];
  117. }];
  118. }
  119. }
  120. - (NSRegularExpression *)expressionForDefinition:(NSString *)definition
  121. {
  122. __block CYRToken *attribute = nil;
  123. [self.tokens enumerateObjectsUsingBlock:^(CYRToken *enumeratedAttribute, NSUInteger idx, BOOL *stop) {
  124. if ([enumeratedAttribute.name isEqualToString:definition])
  125. {
  126. attribute = enumeratedAttribute;
  127. *stop = YES;
  128. }
  129. }];
  130. NSRegularExpression *expression = self.regularExpressionCache[attribute.expression];
  131. if (!expression)
  132. {
  133. expression = [NSRegularExpression regularExpressionWithPattern:attribute.expression
  134. options:NSRegularExpressionCaseInsensitive error:nil];
  135. [self.regularExpressionCache setObject:expression forKey:definition];
  136. }
  137. return expression;
  138. }
  139. @end