I'd like to have a simple UITextView which automatically resizes to fit its content.
With AutoLayout this is quite straightforward: I add the UITextView to my view, set two contraints to anchor the UITextView in the top-left corner, disable scrolling and that's it.
The expected behavior is that the green text view resize its frame each time I type in a character. But this works only partially: for some text, the text view decide that it will render it over two line, instead of just adding the new character at the end of the current line:
I'm guessing this is related to the new TextKit framework, and I played with NSLayoutManager's and NSTextContainer's properties to try to control this behavior, but to no avail.
Note also that if I "hardcode" the width of the text view (for example with a width contraint), the characters are correctly appended at the end of the line, but then I lose the horizontal autoresizing property of the text view.
How can I indicate to the UITextView that I don't want it to break line?
EDIT: After further testing, the bug only seems to appear on 32bits archs.
A bit of a hack (and hoping it's fixed in iOS8) but the following stopped it from happening:
- (void)textViewDidChange:(UITextView *)textView {
// uber hack to stop characters from jumping to the line below
if (textView.text.length == 1 && ![textView.text isEqualToString:#" "]) {
NSRange cursorPosition = [textView selectedRange];
textView.text = [NSString stringWithFormat:#"%# ", textView.text];
textView.selectedRange = cursorPosition;
}
[textView invalidateIntrinsicContentSize];
}
Annoyed me for a while so hope that helps someone else :)
This is apparently now fixed in iOS 8. (Xcode 6.0.1 (6A317)).
Related
I find that in iOS 8 using UITextView sizeThatFits: causes glitchy scrolling behavior. The Text View is constantly scrolling away from the line you are typing on. It seems to be scrolling to the top of the view and then back again.
If it matters, the view is set as an inputAccessoryView.
Via the keyboard I'll type: 1 return 2 return 3 return 4
The TextView the moment before I type 4:
In the delegate method I call sizeThatFits:.
- (void)textViewDidChange:(UITextView *)textView {
[textView sizeThatFits:CGSizeMake(100, 100)];
}
TextView scrolls up to the top. Input happens below the view. Jittery, glitchy scrolling movement up to the top and then back to your line as you type. Input occurs under the keyboard. Extremely annoying.
If I comment out the line:
//[textView sizeThatFits:CGSizeMake(100, 100)];
Now when I type 4 we have nice, smooth typing on the last line:
The UIScrollView sizeThatFits: docs state:
This method does not resize the receiver.
So I'm confused why this would have any effect on the scrolling/input of the textfield.
Is there any way to avoid this glitchy scrolling?
How can you calculate the "height that fits" for a Text View without hitting this bug?
I had the exact same problem and it took me 5 hours to solve this nasty apple bug, I wish I could send them an invoice!
What I end up doing was creating a copy of my original UItextView:
self.textViewCopy = [[UITextView alloc] initWithFrame:self.textView.frame];
[self.textViewCopy setFont:self.textView.font];
And don't add it as a subview.
Then instead call the sizeThatFits on the copy (which will screw up the copy which we don't care about and gets us the information we need):
[self.textViewCopy setText:self.textView.text];
CGSize size = [self.textViewCopy sizeThatFits:CGSizeMake(fixedWidth, CGFLOAT_MAX)];
Using the NSString method sizeWithFont:constrainedToSize: on the text within the UITextView seems to provide a performant alternative to sizeThatFits:.
CGSize preferredSize = [textView.text sizeWithFont:textView.font constrainedToSize:CGSizeMake(CGRectGetWidth(textView.bounds), 200.0)];
It's possible that sizeThatFits: is using sizeWithFont:constrainedToSize: under the hood. Regardless, the iOS glitchy scrolling bug is not reproduced when using the NSString method.
My iOS app is not showing long attributed strings. I have a cell in a tableview which contains this textView. When the text is very long the tableview is unresponsive for a while but when it loads the text is not shown. All other cells are displayed fine. And the textView works fine with small text strings.
Here's the code:
descriptionCell = [tableView dequeueReusableCellWithIdentifier:#"CellAdDetailDescription"];
descriptionCell.bodyTextView.delegate = self;
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:self.ad.body];
UIFont *cellFont;
cellFont = [UIFont fontWithName:#"HelveticaNeue" size:16.0];
NSDictionary *attributesDictionary;
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = 10;
attributesDictionary = #{NSParagraphStyleAttributeName : paragraphStyle , NSFontAttributeName: cellFont};
[str addAttributes:attributesDictionary range:NSMakeRange(0, str.length)];
descriptionCell.bodyTextView.attributedText = str;
I pasted the long string here. I debugged and str is being loaded fine containing the desired text.
Whats wrong here?
What is the max allowed string length in UITextView?
EDIT: very odd, when trying selection in the textView, the text is being shown in the magnifying glass. I posted a video here.
Is it a bug in UITextView?
Here is the screenshot. The blank white at the bottom is the textView.
It could have something to do with the scrolling. If you are showing all the text (i.e. the text view is expanded to be as high as it needs to be, and so is the table view cell), the scrolling is done by the table view. If the text view is smaller, you have to scroll to see all the text - this might cause a conflict with the table view, which is also a scroll view.
It has been suggested that you disable the scrolling of the text view before adding the attributed text and reenable it afterwards. If you are showing the whole text in the table view, you can leave the text view scrolling disabled. In some cases, it will only work if scrolling is enabled. You should check this possibility as well.
I also had this issue (= very long attributed text didn't show up in the UITextView within an autosized UITableViewCell). I tried all accepted answers from here and from this question: UITextView not loading/showing the large text?. None did work.
However I then found out that - in my case - it just works fine on the device. So if you're still struggling with this kind of problem, don't rely on the iOS Simulator.
The reason that you UITextView not show all text because it Frame is too small so it truncates the text to fit with its frame. you can do follow step to show all text:
In your CustomUITableCell, override layoutSubView:
Use this function to calculate size of TextView that fit it content
[textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height
After that, calculate and set new frame for TExtView (in uitableCell custom)
In the tableView:heightForCellAtIndexPath, calculate new size of textView (also height of cell) similar like above and return right height for cell.
I have a non-scrollable UITextView embedded in a UIScrollView and add text to the UITextView dynamically. The UIScrollView adjust it's contentSize accordingly based on the TextView's frame. However, once the UITextView exceeds a height of 8192, the text will become invisible (but still there, because you can use the magnifying glass to highlight text and even see parts of the text through the magnifying glass).
CGRect textviewFrame = self.TextView.frame;
textviewFrame.size.height = [self textViewHeightForAttributedText:self.TextView.attributedText andWidth:320.0];
self.TextView.frame = textviewFrame;
self.ScrollView.contentSize = self.TextView.frame.size;
Helper function to size UITextView accordingly:
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString *)text andWidth:(CGFloat)width
{
UITextView *textView = [[UITextView alloc] init];
[textView setAttributedText:text];
CGSize size = [textView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
Didn't realize it was the same exact problem that was unsolved here until I tested it out explicitly by forcing the max size to 8193 and the problem occurred (while a max size of 8192 still had the text showing correctly). Anyone run into this problem before and know of a work around? Thanks
I was recently hit by this problem and have worked out an effective way around it. Although it seems like an iOS bug IMHO it's really not... there are practical limits to CALayer sizes, plus drawing an 8K high piece of text takes a long time. Much better to do as Apple intended and to only render the bit of text that's visible... that's why UITextView extends UIScrollView after all.
The problem is that UITextView isn't terribly easy to integrate with other bits of UI. In my case I am working on a news app where a single UITextView is used to render the article, plus there's some separate UI (titles and buttons etc) above and below it, all hosted in a single scrollable container.
The solution I've found is to split the UITextView into two views... a dedicated UITextView container whose frame is the full text size (i.e. the same size your UITextView's contentSize) and which is the superview of your UITextView. Your child UITextView's frame should be set to the bounds of the outer scrollable container.
Then all you have to do is use key-value observation to monitor the contentOffset property of your outer scrollable container view (in my case this is a UICollectionView). When its contentOffset changes you update (1) the contentOffset of your UITextView, and (2) the transform property of the UITextView's layer. By updating that transform property the UITextView is fixed to fill the currently-visible part of it's superview. But because you're also updating the UITextView's contentOffset, this trickery is totally invisible to the user... it looks and behaves as if the UITextView is simply very large.
Here's a fully functional solution, for anyone who'd like it!
** Assuming your content size will not exceed the limits of two text views **
This solution works by adding two UITextViews to your view, and splitting your text between them. It looks complicated, but it's actually very simple! I've just written a very verbose description :)
Step 1 - Add two UITextViews to your view:
I added mine in my storyboard. Place them so that one is directly above the other, with no space between them. Don't worry about setting the height (we will set that later).
Set constraints on the views so that they are tied to each other from the top and bottom, and the surrounding edges of the container from all other sides, including your desired padding. i.e. tie the first text view to the container from the top, left, and right, and to the second text view from the bottom. Tie the second text view to the container from the bottom, left, and right, and to the first text view from the top. This will ensure that they stretch appropriately when the content is set.
Don't set any constraints on the height of the views, or if you must (to avoid warnings from the constraints inspector), set the height of one of the views to be >= 20, or some similarly small number.
Disable scrolling, bouncing, and scrolling indicators for both your text views. This solution relies on the views being a fixed, non-scrollable height, so if you'd like your content to scroll, you should use a UIScrollView or UITableViewCell as a container.
Create outlets for your two new text views in your view controller file, naming them something like textView1 and textView2.
Step 2 - Set textContainerInsets to zero:
Set the textContainerInset property on both text views to zero, either using User Defined Runtime Attributes:
or code:
self.textView1.textContainerInset = UIEdgeInsetsZero;
self.textView2.textContainerInset = UIEdgeInsetsZero;
This will ensure that the no visible space will appear between the two views when the content is set, and should not affect the other spacing around your views.
Step 3 - Split your content, set it, and update the view heights:
Simply copy the following code into your view controller file (viewDidLoad), and set the contentString variable to your content.
/* Content is split across two UITextViews to avoid max drawing height */
NSString *contentString = #"Some very long piece of text...";
// Set text
NSArray *components = [contentString componentsSeparatedByString:#"\n"];
NSInteger halfLength = [components count] / 2;
NSArray *firstHalf = [components subarrayWithRange:NSMakeRange(0, halfLength)];
NSArray *secondHalf = [components subarrayWithRange:NSMakeRange(halfLength, [components count] - halfLength)];
NSString *contentString1 = [firstHalf componentsJoinedByString:#"\n"];
NSString *contentString2 = [secondHalf componentsJoinedByString:#"\n"];
self.textView1.text = contentString1;
self.textView2.text = contentString2;
// Set text view heights
CGFloat fixedWidth1 = self.textView1.frame.size.width;
CGFloat fixedWidth2 = self.textView2.frame.size.width;
CGSize newSize1 = [self.textView1 sizeThatFits:CGSizeMake(fixedWidth1, CGFLOAT_MAX)];
CGSize newSize2 = [self.textView2 sizeThatFits:CGSizeMake(fixedWidth2, CGFLOAT_MAX)];
CGRect newFrame1 = self.textView1.frame;
CGRect newFrame2 = self.textView2.frame;
newFrame1.size = CGSizeMake(fmaxf(newSize1.width, fixedWidth1), MIN(newSize1.height, 8192));
newFrame2.size = CGSizeMake(fmaxf(newSize2.width, fixedWidth2), MIN(newSize2.height, 8192));
self.textView1.frame = newFrame1;
self.textView2.frame = newFrame2;
This code splits the contentString roughly in the middle, looking for the nearest newline. If you'd like to split your content on a different character, simply change all occurrences of \n above to whatever you'd like to split on.
Step 4 - Set your container view height:
Set your container view (scrollView, tableViewCell, whatever else) to the height of your two text views, plus whatever additional space you've set above and below them.
CGRect viewFrame = self.myView.frame;
viewFrame.size.height = MIN(self.textView1.frame.size.height, 8192) + MIN(self.textView2.frame.size.height, 8192) + kTextViewContainerPadding;
[self.myView setFrame:viewFrame];
(In my code, kTextViewContainerPadding is a macro I've set to the sum of the space above and below my two text views, within their container).
That's it! Good luck.
Try enabling the scroll for the scrollView.
Keep the height of the textView > height of the content, so that in reality there will be no scroll, but scrollEnabled should be = YES
It solved the problem for me.
Hello I think am not late to answer. I got the same problem like you. This is my solution:
textView.scrollEnabled = YES;
contentTextView.attributedText = finalAttrString;
// contentTextView.text = [attrString string];
contentTextView.font = kFont(contentTextFS + [valueOfStepper intValue]);
[contentTextView sizeToFit];
contentTextView.height += 1;//This is the key code
//contentTextView.height = 8192.0f
Them I solved the trouble and I can change size dynamic.Successfull on iOS 8
I have a UITextView with Content and frame size widths equal to 348, but word wrapping happens whenever text width on a line exceeds 338.61. Does anyone know why this might be happening? How can I access the width that the UITextView uses for word wrapping dynamically?
As of iOS 7, yes. It's a combination of textContainerInset and lineFragmentPadding.
UITextView *textView = ...;
CGFloat wrappingWidth = textView.bounds.size.width - (textView.textContainerInset.left + textView.textContainerInset.right + 2 * textView.textContainer.lineFragmentPadding);
Which is a nice value to know about. You can use it in boundingRectWithSize: calls, for example:
CGRect boundingRect = [text boundingRectWithSize:CGSizeMake(wrappingWidth, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin // this is important
attributes:#{ NSFontAttributeName: textView.font } // and any other attributes of your text
context:nil];
Also cool is the fact that you can set textContainerInset and lineFragmentPadding, so if you want to increase this or have a UITextView that renders with no insets (so it matches a UILabel, for example), you can.
Nothing wrong with the answer above but I have been researching the UITextView word wrapping behavior and have discovered that although one can adjust the contentSize of the textview and the corresponding size of the textContainer, one additional method of changing the text wrap position (when using Auto Layout) is to make the trailing space to the SuperView into a negative value. This will allow one to push the word wrapping feature outward to the right. Not finished with the research just yet, but this tip should help anyone seeking to better control the UITextView word wrapping behavior.
Will post code once I'm complete but for now adjusting the trailing constraint of a UITextView will move the rightmost position of the word/character wrap.
I create an iOS project, which contains only one UITextView full of the whole screen.
And in the controller, I do the things like below
//textView is declared in the .h file which keeps a reference to the UITextView
textView.text = #"";//a long text(over 5000 words), and contains many '\n'
if(textView.text.length > 0) {
NSRange bottom = NSMakeRange(textView.text.length -1, 1);
[textView scrollRangeToVisible:bottom];
}
the textView display a very long text, which will contains 5000 words or more and the text contains many '\n'.
Then I run the project on the simulator iOS 7.0, and scroll from bottom to top. And then the UITextView keeps scrolling to bottom automatically.
Please help me. I have stuck in this problem for a long time.