Scrolling Performance issue with UItextView with large NSAttributedString - ios

I am working on text editor for an app. I am using UITextView
See the sample code to load text view.
// Read text from file (around 300k - 400k words)
NSError *error = nil;
NSString *contentOfFile = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"17254" ofType:#"txt"]
encoding:NSUTF8StringEncoding
error:&error];
// Attributes for text
UIFont *font = [UIFont fontWithName:#"Baskerville" size:36.0f];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentJustified;
NSDictionary *attributes = [[NSDictionary alloc] initWithObjectsAndKeys:font, NSFontAttributeName,
[UIColor blackColor], NSForegroundColorAttributeName,
paragraphStyle, NSParagraphStyleAttributeName, nil];
// Create attributed string
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:contentOfFile attributes:attributes];
// Assign to text view
self.textView.attributedText = attributedString;
The size of text is around 400k words.
I am facing the following issues.
Scrolling of text becomes too slow as i scroll down and some time app crashes due to memory issue. What i think iOS is saving the rendered text image in its memory when textview is scrolled down, but when i scroll up to top it releases the memory.
If i tap "Select All" it takes too much time to select the text and after text selection the scrolling becomes poor and some times app crashes due to memory issue because its memory increases. I think iOS generates the image of complete text(as if its visible to user) in its memory and then selects the complete text and retain its image until selection is finished. After selection is finished memory retained by app drops.
The other way of displaying the large text is to use the multiple textviews and assign the text to visible textview only like UITableView, but this will increase the complexity as i have to recount the number of textviews required on each textChanged delegate call of layoutManager of UItextView.
Any body has idea how to display large attributed text in UITextView with better performance.
Any guess how iPages app is working, because it display text when the area is in visible range.

You should not load entire text directly. You should load the text which is twice as much as the textview capacity.
Now track the scroll event and modify the string dynamically.

Related

NSLineBreakByTruncatingTail on UITextView removing new line breaks

I have a UITextView with attributedText that has multiple new line breaks included in its attributed string.
NSMutableAttributedString *info = [[NSMutableAttributedString alloc] initWithString:#""];
NSAttributedString *title = [[NSAttributedString alloc] initWithString: [NSString stringWithFormat:#"%#\n", item.title] attributes:titleAttributes];
NSAttributedString *description = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%#\n\n", description] attributes:descriptionAttributes]
[info appendAttributedString:bioItemTitle];
[info appendAttributedString:bioItemDescription];
textView.attributedText = info;
I've set the lineBreakMode of the textView's textContainer to NSLineBreakByTruncatingTail.
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
The textView's textContainer also has a maximum number of lines.
textView.textContainer.maximumNumberOfLines = 8;
The problem arises when the 8th line of the textView is a new line, and not a line of characters. The textContainer truncates by removing the new line and replacing it with the next line of written characters.
How do I preserve the new line while still setting a lineBreakMode?
See screenshots
Try using UILabel instead of UITextView. It seems to play nicer in regard to truncating a new line.
Random things to try on the off chance you haven't already that aren't even solutions but maybe workarounds, and may not work anyway, but whatever:
Change the maximumNumberOfLines to 0 and rely on frames/autolayout to size the text view and its container, perhaps in combination with setting the text container's heightTracksTextView property to true
Insert horizontal whitespace or invisibles at the beginning of lines that otherwise only contain a newline #hacky

iOS Swift Wrapping Text Around Image

I have a UILabel that will contain various lengths of text. I need to place an image in the upper left corner of the text and have the text wrap around it. How can I do this? All I could find was using a UITextView which I don't want to use since it's static text.
This is a perfectly reasonable use of a UITextView. Your reasons for hesitation to use it are unclear. You can make the UITextView non-editable and non-selectable; the user will not know that it is a UITextView as opposed to to a UILabel.
If you don't like that solution, then what I would do is use, instead of a UILabel, a custom view that draws the text. You can draw the text with Text Kit and thus you can take complete charge of how the text draws. In particular, you can cause it to wrap however you like, including not drawing the text in the corner (exclusion path on the text container).
You can achieve this using NSTextAttachment and attributed text.
NSMutableAttributedString *myText = [[NSMutableAttributedString alloc] initWithString:labelStr];
NSTextAttachment *attachment = [[NSTextAttachment alloc] init]
attachment.image = yourImage;
NSAttributedString *attachmentLock = [NSAttributedString attributedStringWithAttachment:attachment];
NSMutableAttributedString *lockString = [[NSMutableAttributedString alloc] initWithAttributedString:myText];
//set your image range within the text. modify it till you get it right.
NSRange range = NSMakeRange(0,[labelStr length]);
[lockString replaceCharactersInRange:NSMakeRange(range.location, 1) withAttributedString:attachmentLock];
yourLabel.attributedText = lockString;

Text Formatting Using UITextView

I have a UITextView where user enter data. I have a format bar below which has
bold, italic, underline and other options like alignment etc.
After a lot of search and testing I came to know that NSString don't support such bold, italic styling instead NSAttributedString should be used but I use NSAttributedString I am unable to make the selected text bold and italic at the same time.
My code is like this
NSMutableAttributedString *textViewText = [[NSMutableAttributedString alloc]initWithAttributedString:textView.attributedText];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
if(textBoldBtn){
UIFont *boldFont = [UIFont boldSystemFontOfSize:textView.font.pointSize];
NSDictionary *boldAttr = [NSDictionary dictionaryWithObject:boldFont forKey:NSFontAttributeName];
[dict addEntriesFromDictionary:boldAttr];
}
if(textItalicBtn){
UIFont *italicFont = [UIFont italicSystemFontOfSize:textView.font.pointSize];
NSDictionary *italicAttr = [NSDictionary dictionaryWithObject:italicFont forKey:NSFontAttributeName];
[dict addEntriesFromDictionary:italicAttr];
}
attributedText = [[NSMutableAttributedString alloc] initWithString:text attributes:dict];
[textViewText appendAttributedString:attributedText];
textView.attributedText = textViewText;
return true;
}
Using this technique it only takes the last font in dict when both buttons are pressed.
I have gone through a link on stack overflow where they use fontDescriptor of Label to make the text both italic and bold but note I don't want to use any label. I am using UITextView. I have also gone through
[textView setAllowsEditingTextAttributes:YES];
But I want my own functions. I have also seen several EGOTextView and TextEdit for iOS but I strictly want this for iPad. I only want to use UITextView.
Kindly tell if there is any way using attributed string to make text both italic bold and even underline at the same time or any way to customize the functions of textView personal EditingTextAttributes functionality.
Thanks in Advance.
*App has to be uploaded to app store so no private frameworks required because they would cause rejection of app.
You have a some issues.
First:
Attributes are a NSDictionary. That means works with key/value, and the key is unique!
So when, you use addEntriesFromDictionary:, the doc says:
If both dictionaries contain the same key, the receiving dictionary’s
previous value object for that key is sent a release message, and the
new value object takes its place.
And when you want to apply bold and italic effect in your code, you think that you have:
Key — Value
NSFontAttributeName — theBoldFont
NSFontAttributeName — theItalicFont
Whereas you replace the bold font with the italic one.
Second:
Since Bold AND Italic are a attribute "hidden" in UIFont, if you want to apply the both effect, you have to find a font which is italic AND bold. That why, underlining (NSUnderlineAttributeName) can be added without encountering your issue with bold and italic.
You may look there to know how to do about it.
So you may code some logic like this:
if (textItalicBtn && textBoldBtn)
{}
else if (textItalicBtn)
{}
else if (textBoldBtn)
{}

CoreText Attributed String Height Calculation Inaccurate

CoreText isn't giving the correct height of the attributed string (its short by a line or more). I have seen a lot of posts on SO about this but unable to understand or find a solution. Can somebody explain how Core Text height calculation works? Here's an example code I wrote showing inaccurate height calculation.
Context
I have a collection view where the cell's height is determined by the content inside it.
I am displaying paragraphs of text in the cells. I would like to save some performance by doing the height calculation using core text. I have seen that with core text's height calculation I could save ~300ms.
Code
// Height Calculation
+ (CGFloat)getHeight
{
NSString *text = #"The Apple HIG recommends to use a common color for links and buttons and we did just that. By using the same color throughout the app we trained the user to always associate blue to a link.The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.";
NSAttributedString *attrStr = [self attributedString:text withLinespacing:3 withLineBreakMode:NSLineBreakByWordWrapping];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrStr));
CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter,
CFRangeMake(0, attrStr.length),
NULL,
CGSizeMake(320, 9999),
NULL);
return suggestedSize.height;
}
// Load the same text when Cell is about to display
- (void)loadData
{
NSString *text = #"The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.";
NSAttributedString *attrStr = [[self class] attributedString:text withLinespacing:3 withLineBreakMode:NSLineBreakByWordWrapping];
// UILabel element
self.textLabel.attributedText = attrStr;
self.layer.borderColor = [UIColor blueColor].CGColor;
self.layer.borderWidth = 1.0f;
}
// Generate attributed string with leading, font and linebreak
+ (NSAttributedString *)attributedString:(NSString *)string
withLinespacing:(CGFloat)linespacing
withLineBreakMode:(NSLineBreakMode)lineBreakMode
{
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:string];
NSInteger strLength = [string length];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = linespacing;
style.lineBreakMode = lineBreakMode;
[attrStr addAttributes:#{NSParagraphStyleAttributeName: style,
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue" size:15]} range:NSMakeRange(0, strLength)];
return attrStr;
}
The above code uses core text to calculate the height and UILabel to display the text. The UILabel has 3 constraints to the cell {Top:17, Leading:13px, Trailing:13px}
CTFramesetterSuggestFrameSizeWithConstraints is known to be buggy, returning incorrect height values. The missing line bug you experience is very common, and there are no good solutions that I know of, only ugly workarounds which never give 100% accurate results.
For iOS7 and above, I recommend moving to TextKit. Somehow the calculations performed there internally do work correctly, while being based on Core Text also. Using NSLayoutManager's usedRectForTextContainer: returns a correct result.
You can see a more complete answer here. While not exactly 100% on topic, there is some discussion about the bugginess of Core Text calculations.

how to set a UITextView's text to be bolded and not clickable

I have the following HTML in a UITextView and would like to render it into a UITextView
is my body for the note
food item - more item stuff;`
Let me add: it's currently showing as blue and underlined and not clickable. I would like to make it bolded and not clickable. I have read the docs regarding linkTextAttributes but, not having used this, it is a bit beyond me and I don't really see any easy way to manipulate this. How would I just render the above link bolded and black (not blue) and maintain the non-clickable nature?
UPDATE (solution using UITextView's linkTextAttributes)
self.testTextView.editable = NO;
self.testTextView.selectable = YES;
self.testTextView.userInteractionEnabled = NO; // workaround to disable link - CAUTION: it also disables scrolling of UITextView content
self.testTextView.dataDetectorTypes = UIDataDetectorTypeLink;
self.testTextView.linkTextAttributes = #{NSFontAttributeName : [UIFont boldSystemFontOfSize:14.0f], // NOT WORKING !?
NSForegroundColorAttributeName : [UIColor redColor]};
...
self.testTextView.text = #"Lorem ipsum http://www.apple.com Lorem ipsum";
As you can see in comments, I wasn't able to set new font to linkTextAttributes, though the colour attribute was working as expected.
If you can get away with colour attribute or some other text attribute to style your URLs and you don't have to worry about disabled UITextView scrolling, then this may be your solution.
PREVIOUS (alternative solution)
If you're using Storyboard/xib then make sure you've deselected Detection -> Links for your UITextView. You can make your link bold by setting its container font to some bold typeface. If you want to support different text/font styles in one string object then you should really look for NSAttributedString or NSMutableAttributedString.
See: https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/classes/NSAttributedString_Class/Reference/Reference.html.
Example:
UIFont *linkFont = [UIFont fontWithName:#"SomeBoldTypeface" size:12];
NSString *link = #"food item - more item stuff";
NSMutableAttributedString *someString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:#"is my body for the note %#; let me ad", link]];
[someString addAttribute:NSFontAttributeName value:linkFont range:NSMakeRange(24, link.length)];
UITextView *textView = [[UITextView alloc] init];
textView.attributedText = someString;
...

Resources