New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Crash in (NSRange)_effectiveRangeOfOutermostTextBlocksInRange:(NSRange)range #606
Comments
@brovador Please add a category -[NSString objectForKey:] and set a breakpoint on that to determine what string this is that this is tried to be called on. |
@Cocoanetics Added the category, printing on the debugger the instance that is receiving the selector we have this: (lldb) po self CTFont CTFontDescriptor {type = mutable dict, count = 1, entries => 1 : {contents = "NSFontNameAttribute"} = {contents = "Palatino-Roman"} } And printing the value of (lldb) po key DTTextBlocks (lldb) po [key class] __NSCFConstantString The screenshot: I hope it helps |
@brovador No idea. Does that mean that you found a case where self is a string and this string is set as text block?! |
I've added a breakpoint in my category in the method |
@brovador please inspect the attributed string what class the object has behind the DTTextBlock key. |
After several tries, not always enter in the category, here is other break (BAD_ACCESS in this case) of the same method: (lldb) print index (NSUInteger) $3 = 6372 (lldb) print &effectiveRangeOfBlocksArray (NSRange *) $4 = location=6363, length=57366424 (lldb) print DTTextBlocksAttribute (NSString *const) $5 = 0x00508020 @"DTTextBlocks" (lldb) print _attributedStringFragment (NSAttributedString *) $6 = 0x0b5d6ec0 @"La señora aceleró el paso con sus tres hijos, Daoud, Hiba y Hasan Taha, a través de la avenida. No en vano, esta vía se ganó una triste fama durante meses, al ser elegida por un temible francotirador que -dicen- abatió a decenas de personas. «Es el primer año que pueden venir al colegio porque en el año 2012 era imposible cruzar esa calle. Disparaban continuamente. Ahora es esporádico y podemos correr el riesgo. Hasan estaba hoy feliz, como si fuera su cumpleaños», explica Umm Daoud, la madre de los tres niños, nada más llegar al colegio. Con más de dos decenas de colegios arrasados por los bombardeos, la ONG local Nashata ha conseguido habilitar tres aulas en un edificio a medio construir y otras tantas en una vivienda particular abandonada. El sábado fue el primer día del segundo año lectivo que apadrinan en la castigada ciudad siria de Deir Ezzor. «Usamos el sótano para los más pequeños y p" |
@brovador That's not what I asked. Please get the attributes at the specified index and check if the blocks attribute has the correct class. |
Ok, sorry, checking it in both threads: In thread 23: (lldb) print index (NSUInteger) $22 = 6372 (lldb) po [_attributedStringFragment attributesAtIndex:index effectiveRange:&effectiveRange] { CTForegroundColor = " [ (kCGColorSpaceDeviceRGB)] ( 0.223529 0.223529 0.223529 1 )"; NSFont = "CTFont \nCTFontDescriptor {type = mutable dict, count = 1,\nentries =>\n\t1 : {contents = \"NSFontNameAttribute\"} = {contents = \"Palatino-Roman\"}\n}\n>"; NSParagraphStyle = "CTParagraphStyle:\nbase writing direction = -1, alignment = 3, line break mode = 0, default tab interval = 36\nfirst line head indent = 0, head indent = 0, tail indent = 0\nline height multiple = 0, maximum line height = 0, minimum line height = 0\nline spacing adjustment = 0, paragraph spacing = 18, paragraph spacing before = 0\n"; } (lldb) po [_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:index effectiveRange:&effectiveRange] nil In thread 25: (lldb) print index (NSUInteger) $25 = 6371 (lldb) po [_attributedStringFragment attributesAtIndex:index effectiveRange:&effectiveRange] { CTForegroundColor = " [ (kCGColorSpaceDeviceRGB)] ( 0.223529 0.223529 0.223529 1 )"; NSFont = "CTFont \nCTFontDescriptor {type = mutable dict, count = 1,\nentries =>\n\t1 : {contents = \"NSFontNameAttribute\"} = {contents = \"Palatino-Roman\"}\n}\n>"; NSParagraphStyle = "CTParagraphStyle:\nbase writing direction = -1, alignment = 3, line break mode = 0, default tab interval = 36\nfirst line head indent = 0, head indent = 0, tail indent = 0\nline height multiple = 0, maximum line height = 0, minimum line height = 0\nline spacing adjustment = 0, paragraph spacing = 18, paragraph spacing before = 0\n"; } (lldb) po [_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:index effectiveRange:&effectiveRange] nil |
We are not getting anywhere like this. Can you please Skype me at TheDrops? |
Ok perfect! |
Hi Oliver and thanks a lot for the support, really. I've created a small project that reproduces the issue: https://github.com/brovador/DTCoreTextLayoutExceptionExample In the example, I reproduced the same situation that in my project, creating a column based text. It seems that the issue is related to when is creating the TextContentViews for every column. It's not easy to reproduce, in our real project, we have a collection view of articles and the crash comes randomly when we are creating the cell for the article with the columns. To recreate the same situation in a easy way, I've created a timer inside the ViewController with a button to regenerate the content (creating all the text columns etc). But it's only to force the error, because it's not even necessary the error can be thrown during application launch too. I think the issue comes by drawing the columns, it's like two columns are being drawn at the same time and it causes the crash. If I inspect both threads, they have the same attributedstring and each one corresponds to one of the columns. The code in the example that I'm using to draw text layouted in columns: DTHTMLAttributedStringBuilder *stringBuilder = [[DTHTMLAttributedStringBuilder alloc] initWithHTML:[text dataUsingEncoding:NSUTF8StringEncoding] options:nil documentAttributes:NULL]; DTCoreTextLayouter *layouter = [[DTCoreTextLayouter alloc] initWithAttributedString:[stringBuilder generatedAttributedString]]; NSInteger textRangeInit; DTCoreTextLayoutFrame *layoutFrame; DTAttributedTextView *textView; //Column1 textRangeInit = 0; layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column1.size} range:NSMakeRange(textRangeInit, 0)]; textView = [[DTAttributedTextView alloc] initWithFrame:column1]; [textView setAttributedString:[stringBuilder generatedAttributedString]]; [[textView attributedTextContentView] setLayoutFrame:layoutFrame]; [self.view addSubview:textView]; //Column2 textRangeInit = [layoutFrame visibleStringRange].location + [layoutFrame visibleStringRange].length; layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column2.size} range:NSMakeRange(textRangeInit, 0)]; textView = [[DTAttributedTextView alloc] initWithFrame:column2]; [textView setAttributedString:[stringBuilder generatedAttributedString]]; [[textView attributedTextContentView] setLayoutFrame:layoutFrame]; [self.view addSubview:textView]; //Column3 textRangeInit = [layoutFrame visibleStringRange].location + [layoutFrame visibleStringRange].length; layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column3.size} range:NSMakeRange(textRangeInit, 0)]; textView = [[DTAttributedTextView alloc] initWithFrame:column3]; [textView setAttributedString:[stringBuilder generatedAttributedString]]; [[textView attributedTextContentView] setLayoutFrame:layoutFrame]; [self.view addSubview:textView]; The same exception raised in the example: I hope it helps |
I've updated the example above, using a button to regenerate the content better than a timer. |
Ok, after several tries, I finally found a workaround and more info about the problem (updated on my test project). The issue happens when all the textviews created for each column share the same StringBuilder in the layouter. If I change the code to create a new instance of the stringbuilder and the layouter for each column, the issue does not appear. In my project example I've added a switch to enable and disable de creation of all the elements for each column: DTHTMLAttributedStringBuilder *stringBuilder; DTCoreTextLayoutFrame *layoutFrame; DTAttributedTextView *textView; DTCoreTextLayouter *layouter; stringBuilder = [[DTHTMLAttributedStringBuilder alloc] initWithHTML:[text dataUsingEncoding:NSUTF8StringEncoding] options:nil documentAttributes:NULL]; layouter = [[DTCoreTextLayouter alloc] initWithAttributedString:[stringBuilder generatedAttributedString]]; //Column1 textRangeInit = 0; layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column1.size} range:NSMakeRange(textRangeInit, 0)]; textView = [[DTAttributedTextView alloc] initWithFrame:column1]; [textView setAttributedString:[stringBuilder generatedAttributedString]]; [[textView attributedTextContentView] setLayoutFrame:layoutFrame]; [self.vTextContainer addSubview:textView]; //Column2 textRangeInit = [layoutFrame visibleStringRange].location + [layoutFrame visibleStringRange].length; if (createAllContent) { stringBuilder = [[DTHTMLAttributedStringBuilder alloc] initWithHTML:[text dataUsingEncoding:NSUTF8StringEncoding] options:nil documentAttributes:NULL]; layouter = [[DTCoreTextLayouter alloc] initWithAttributedString:[stringBuilder generatedAttributedString]]; } layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column2.size} range:NSMakeRange(textRangeInit, 0)]; textView = [[DTAttributedTextView alloc] initWithFrame:column2]; [textView setAttributedString:[stringBuilder generatedAttributedString]]; [[textView attributedTextContentView] setLayoutFrame:layoutFrame]; [self.vTextContainer addSubview:textView]; //Column3 textRangeInit = [layoutFrame visibleStringRange].location + [layoutFrame visibleStringRange].length; if (createAllContent) { stringBuilder = [[DTHTMLAttributedStringBuilder alloc] initWithHTML:[text dataUsingEncoding:NSUTF8StringEncoding] options:nil documentAttributes:NULL]; layouter = [[DTCoreTextLayouter alloc] initWithAttributedString:[stringBuilder generatedAttributedString]]; } layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column3.size} range:NSMakeRange(textRangeInit, 0)]; textView = [[DTAttributedTextView alloc] initWithFrame:column3]; [textView setAttributedString:[stringBuilder generatedAttributedString]]; [[textView attributedTextContentView] setLayoutFrame:layoutFrame]; [self.vTextContainer addSubview:textView]; if the variable I think that it's something related with the string generated by the stringbuilder or something with the stringbuilder itself. As workaround I can create the stringbuilder and layouter for each column but it will be very less efficient that having a single instance shared between all the textviews that creates the columns of the text. |
I fear that enumerations of NSAttributedString attributes might not be thread-safe. A solution would be to determine the ranges of text blocks once for the entire layout frame. Best regards @Cocoanetics on Twitter and App.net
|
Right @Cocoanetics, calculating first the ranges for the layout frame and assigning the substrings to the columns the issue didn't appear: stringBuilder = [[DTHTMLAttributedStringBuilder alloc] initWithHTML:[text dataUsingEncoding:NSUTF8StringEncoding] options:nil documentAttributes:NULL]; attrString = [stringBuilder generatedAttributedString]; layouter = [[DTCoreTextLayouter alloc] initWithAttributedString:attrString]; NSRange range1, range2, range3; textRangeInit = 0; layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column1.size} range:NSMakeRange(textRangeInit, 0)]; range1 = [layoutFrame visibleStringRange]; textRangeInit = range1.location + range1.length; layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column2.size} range:NSMakeRange(textRangeInit, 0)]; range2 = [layoutFrame visibleStringRange]; textRangeInit = range2.location + range2.length; layoutFrame = [layouter layoutFrameWithRect:(CGRect){.origin = CGPointZero, .size = column3.size} range:NSMakeRange(textRangeInit, 0)]; range3 = [layoutFrame visibleStringRange]; //Column1 textView = [[DTAttributedTextView alloc] initWithFrame:column1]; [textView setAttributedString:[attrString attributedSubstringFromRange:range1]]; [self.vTextContainer addSubview:textView]; //Column2 textView = [[DTAttributedTextView alloc] initWithFrame:column2]; [textView setAttributedString:[attrString attributedSubstringFromRange:range2]]; [self.vTextContainer addSubview:textView]; //Column3 textView = [[DTAttributedTextView alloc] initWithFrame:column3]; [textView setAttributedString:[attrString attributedSubstringFromRange:range3]]; [self.vTextContainer addSubview:textView]; This it's probably the best solution. The layouter and the attributedstringbuilder not need to be regenerated for every column and the code is now thread-safe. For me it's a good workaround to my problem, thanks a lot. I've created a new example project using this technique: https://github.com/brovador/DTCoreTextColumnsExample |
Closing this as it does not require a change to DTCoreText. |
I'm going to implement a serial queue to avoid doing two enumerations on different threads at the same time. |
I've implemented additional synchronization to prevent multiple processes from enumerating the text boxes at the same time. @brovador it would be great if you could try the current develop version and verify that this indeed fixes the issue. |
Ok, I will check it against my test project and verify it. Thanks! |
@Cocoanetics , I've uploaded again the sample project here: https://github.com/brovador/DTCoreTextExceptionExample I was trying with this Podfile, but error still happens: platform :ios, '5.0' pod 'DTCoreText', :git => 'https://github.com/Cocoanetics/DTCoreText.git', :branch => 'develop' pod 'DTFoundation', :git => 'https://github.com/Cocoanetics/DTFoundation.git', :branch => 'develop' You can download and use my sample project to test or debug the issue. I hope it helps |
@brovador please post a screen shot of the threads call stacks with all threads expanded. Also, please tell me which Xcode, Sim version you use to reproduce this. |
@brovador Turns out that I needed to synch on the _framesetter or globally. Apparently even though the layoutFrame, the view and the attributed string are separate instances, if you enumerate on overlapping regions you get a crash. Synchronizing on the view class or the _framesetter worked. For safety I opted to do it on the content view globally. please re-test and comment. |
Tested over my sample project and now works perfect, Thanks! |
Versions tested:
pod 'DTCoreText', '~>1.6.6'
andpod 'DTCoreText', :head
We are rendering text in columns inside a collection view. When we build the cell with the column layout (sometimes, with more frequency on simulator), we receive a crash in
DTCoreTextLayoutFrame
in the method:Stacktrace:
In Xcode5, the console shows the following stacktrace:
The text was updated successfully, but these errors were encountered: