0
votes

My application uses my own subclass of NSTextStorage and everything worked fine until Mavericks. Even with the most stripped-down version, I get an exception as soon I init a text view with it. I read the documentation and also looked at How to extend NSTextStorage?. Hopefully one of you knows if Apple changed something or if I'm doing something wrong.

Info you probably need to help me:

the exception:

TextViewCheck[12542:303] : Exception *** NSRunStorage (0x6000000a4200), _replaceElements(): replaced range {0, 929} extends beyond current run storage size 928. raised during typesetting layout manager NSLayoutManager: 0x100106a80
1 containers, text backing has 928 characters selected character range {0, 0} affinity: upstream granularity: character marked character range {0, 0} Currently holding 928 glyphs.
Glyph tree contents: 928 characters, 928 glyphs, 1 nodes, 64 node bytes, 960 storage bytes, 1024 total bytes, 1.10 bytes per character, 1.10 bytes per glyph Layout tree contents: 928 characters, 928 glyphs, 0 laid glyphs, 0 laid line fragments, 1 nodes, 64 node bytes, 0 storage bytes, 64 total bytes, 0.07 bytes per character, 0.07 bytes per glyph, 0.00 laid glyphs per laid line fragment, 0.00 bytes per laid line fragment , glyph range {0 928}. Ignoring...

the backtrace

thread #1: tid = 0x137013, 0x00007fff8ad7e44e AppKit`_NSGlyphTreeGetGlyphsInRange + 1179, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1, address=0x1002c0000)

frame #0: 0x00007fff8ad7e44e AppKit`_NSGlyphTreeGetGlyphsInRange + 1179
frame #1: 0x00007fff8ad7dfa1 AppKit`-[NSLayoutManager getGlyphsInRange:glyphs:characterIndexes:glyphInscriptions:elasticBits:bidiLevels:] + 92
frame #2: 0x00007fff8ad4d8b6 AppKit`-[NSATSGlyphStorage setGlyphRange:characterRange:] + 3724
frame #3: 0x00007fff8ad4c8be AppKit`-[NSATSTypesetter _ctTypesetter] + 306
frame #4: 0x00007fff8ad4baf9 AppKit`-[NSATSLineFragment layoutForStartingGlyphAtIndex:characterIndex:minPosition:maxPosition:lineFragmentRect:] + 85
frame #5: 0x00007fff8ad4a64e AppKit`-[NSATSTypesetter _layoutLineFragmentStartingWithGlyphAtIndex:characterIndex:atPoint:renderingContext:] + 2777
frame #6: 0x00007fff8ad7dc79 AppKit`-[NSATSTypesetter layoutParagraphAtPoint:] + 149
frame #7: 0x00007fff8b3f5b45 AppKit`-[NSTypesetter _layoutGlyphsInLayoutManager:startingAtGlyphIndex:maxNumberOfLineFragments:maxCharacterIndex:nextGlyphIndex:nextCharacterIndex:] + 4043
frame #8: 0x00007fff8ad7cc9d AppKit`-[NSTypesetter layoutCharactersInRange:forLayoutManager:maximumNumberOfLineFragments:] + 202
frame #9: 0x00007fff8ad7cb81 AppKit`-[NSATSTypesetter layoutCharactersInRange:forLayoutManager:maximumNumberOfLineFragments:] + 1107
frame #10: 0x00007fff8ad7b219 AppKit`-[NSLayoutManager(NSPrivate) _fillLayoutHoleForCharacterRange:desiredNumberOfLines:isSoft:] + 1306
frame #11: 0x00007fff8ae24ad9 AppKit`_NSFastFillAllLayoutHolesForGlyphRange + 1435
frame #12: 0x00007fff8ae6ddbd AppKit`-[NSLayoutManager(NSPrivate) _firstPassGlyphRangeForBoundingRect:inTextContainer:okToFillHoles:] + 309
frame #13: 0x00007fff8ae6cea4 AppKit`-[NSLayoutManager(NSPrivate) _glyphRangeForBoundingRect:inTextContainer:fast:okToFillHoles:] + 875
frame #14: 0x00007fff8add81b2 AppKit`-[NSTextView setNeedsDisplayInRect:avoidAdditionalLayout:] + 1466
frame #15: 0x00007fff8acb4222 AppKit`-[NSView setNeedsDisplay:] + 81
frame #16: 0x00007fff8add68d9 AppKit`-[NSTextView setTextContainer:] + 699
frame #17: 0x00007fff8add651a AppKit`-[NSTextContainer setTextView:] + 263
...

the code:

TTTSimpleTextStorage.m

@implementation TTTSimpleTextStorage

- (id)initWithAttributedString:(NSAttributedString *)attrStr {
    if (self = [super init]) {
        contents = attrStr ? [attrStr mutableCopy] :
        [[NSMutableAttributedString alloc] init];
    }
    return self;
}


- init {
    return [self initWithAttributedString:nil];
}

- (NSString *)string {
    return [contents string];
}

- (NSDictionary *)attributesAtIndex:(NSUInteger)location
                 effectiveRange:(NSRange *)range {
    return [contents attributesAtIndex:location effectiveRange:range];
}

- (void)replaceCharactersInRange:(NSRange)range withString:(NSString
                                                        *)str {
    NSInteger origLen = [self length];
    [contents replaceCharactersInRange:range withString:str];
    [self edited:NSTextStorageEditedCharacters range:range
          changeInLength:[self length] - origLen];
}

- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range {
    [contents setAttributes:attrs range:range];
    [self edited:NSTextStorageEditedCharacters range:range
       changeInLength:0];
}

@end

How I use it

This is in the MainWindowController - awakeFromNib, the window IBOutlet has a NSView as subview named container and a random string string1

nts1 = [[TTTSimpleTextStorage alloc] initWithString:string1];

NSLayoutManager* lm1 = [[NSLayoutManager alloc] init];
[nts1 addLayoutManager:lm1];

NSTextContainer* tc1 = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(400, 280)];
[lm1 addTextContainer:tc1];


tv1 = [[NSTextView alloc] initWithFrame:NSMakeRect(40, 20, 400, 280) textContainer:tc1];  // here it crashes

[self.container addSubview:tv1];
[self.window makeKeyAndOrderFront:nil];
[self.window makeFirstResponder:tv1];
1
It looks like an off-by-one error in one of your ranges.Brad Allred
@BradAllred: right, but I don't know where I create that error or how I can solve it. apparently the string I use has 928 characters and until that crash only the methods -(id)init..., -(NSString*)string and -(NSDictionary*)attributesAtIndex... get called. and in the attributesAtIndex the range is (928,0)Tony J Stark
comment out all your overrides except the first and run it. if it doesn't crash add the next method and repeat till you find the culprit. my money is on replaceCharactersInRangeBrad Allred
thanks for your answers. I experimented a bit but all those methods have to be overwritten not only according to the documentation, it doesn't compile without them at all. I tried to return different stuff (like manipulating the returned range, checking indices for running over) but I discovered that replaceCharactersInRange doesn't get called. only string and attributesAtIndex.Tony J Stark

1 Answers

0
votes

In replaceCharactersInRange, you are setting origLen to [self length], and one line later you're calculating the changeInLength by subtracting origLen with [self length], which is effectively:

NSInteger origLen = self.length;
// ...
changeInLength: origLen - self.length

I think you rather want:

changeInLength: self.length - str.length];