Simply calling UITextView `sizeThatFits:` causes glitchy scrolling / input behavior? - ios

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.

Related

UITextView content offset changes after setting frame

I'm building a view that's very similar to the messages app - I have a subview at the bottom of the page with a UITextView in it and as the user types and reaches the end of the line the text view as well as the view containing it should expand upward.
The way I have it working is that in the textViewDidChange: method I call my layout function, and that does
CGFloat textViewWidth = 200;
CGFloat textViewHeight = [self.textView sizeThatFits:CGSizeMake(textViewWidth, 2000)].height;
[self resizeParentWithTextViewSize:CGSizeMake(textViewWidth, textViewHeight)];
// And then the resize parent method eventually calls
textView.frame = CGRectMake(10, 10, textViewWidth, textViewHeight);
The problem is that when typing at the end of line and the view expands, I end up with an arbitrary contentOffset.y of something like 10.5 on the text view so the text is all shifted up to the top of the view. Weirdly, it's alternating on every other line, so expanding the first time leaves the y content offset shifted up, then at the next line it's close to zero, then back to 10.5 on the next line, etc. (not sure if that's helpful or just a strange artifact of my values). I can set it back to zero afterwards but it looks terrible because there's a brief flash where the text has the offset value and then it gets shifted back to the middle.
I've read that it's usually better to use content insets for scroll views rather than changing the frame, but I don't get how to do that because I do need to change the frame size as well.
How can I resize the UITextView without this happening? I think I can get by with setting the text view not to be scrollable and that fixes the issue, but I'd like to understand what's going on.
The problem is that UITextView's scroll animation and your frame setting action were happened at the same time.
UITextView internally scrolls the texts you currently typing to visible when typed one more character at the end of the line or typed the new line character. But the scroll animation does not need because you are expanding the textview. Unfortunately we can't control textview's internal scroll action so the text scrolls to the top of the expanded textview weirdly. And that weird scroll makes unnecessary bottom padding too.
You can avoid this weird action very simply with overriding UITextView's setContentOffset:animated: like this.
Objective-C
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
[super setContentOffset:contentOffset animated:NO];
}
Swift
override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
super.setContentOffset(contentOffset, animated: false)
}
This code avoids the auto sizing UITextView's unnecessary scroll animations and you can expand the size of the text view freely.
Setting textView.scrollable = NO lets me resize the text view without any strange offsets, that's the only way I've been able to figure out. And I guess it's not too much of a limitation for common scenarios, if you want the text view to be scrollable you probably don't need to resize it on the fly since the user can scroll around as the content changes.
I confronted the same issue: changing the UITextView's frame to fit its content had a side effect on the scroll position being wrong. The UITextView scrolled even when the contentSize was fitting the bounds.
I ended up with setting scrollEnabled to true and with rolling the content offset back if the UITextView is not actually scrollable
override var contentOffset: CGPoint {
didSet {
if iOS8 {
// here the contentOffset may become non zero whereas it shouldn't be
if !isContentScrollable && contentOffset.y > 0 {
contentOffset.y = 0
}
}
}
}
var isContentScrollable: Bool {
let result = ceil(contentSize.height) > ceil(height)
return result
}
Actually, I faced the same issue and found that actually this happens only when UITextView has any Autolayout constraints. If you will try to use UITextView without applying any Constraint then this will not happen and will work fine. but, as you apply Auto layout constraints it automatically scrolls to bottom. To deal with this I just add method
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
self.textView.contentOffset = CGPointZero;
}

iOS8 - keyboard input accessory view with dynamic height

We have a UITextView with a keyboard input accessory - the accessory is another UIView with a few buttons and another UITextView that grows in height as needed to display a message. (similar to what you see in iMessage)
Everything works fine up through iOS7 and the input accessory grows upward above the keyboard when we update the frame size. But with iOS8, the accessory view grows downward extending over the predictive text and keyboard.
Is there a new way to tell the iOS8 keyboard view to relayout the accessory views? I've tried calling ReloadInputViews() and it doesn't seem to change anything.
Stuck on this - thanks for the help.
I override the addConstraint method on my view as apple sets a constraint with constant height for iOS8. This seems to solve the issue.
I meet this problem too. What I do is override inputAccessoryView's layoutSubviews method and make the height is a fixed number. like this:
- (void)layoutSubviews {
if (self.height > 38) {
self.height = 38;
}
}
PS:
what strange is when your inputAccessoryView's height is above 50,inputAccessoryView will not grows downward.

UITextView doesn't scroll sometimes

While typing in a UITextView sometimes it scrolls down to current line(case a) but it doesn't the other times(case b).
There's another problem which is:
The same UITextView sometimes show all the text in it (case 1) but other times it doesn't show the last line of text(case 2).
Whenever case 1 happens case a follows.
and Whenever case 2 happens case b follows.
This is the hierarchy of the view:
Size(variable height-fixed width) of these UITextViews as well as UICollectionViewCells are calculated using sizeWithFont:constrainedToSize:lineBreakMode:
Limits of height are set from 43 to 120.
if height>43 then enableScrolling is set to YES, otherwise to NO(Logic X).
Scrolling is enabled when textViewBeginEditing and Logic X is applied when textViewEnded Editing.
There is no scrolling in case 2.
Please suggest cause and workarounds.
On iOS7, I think that you could leave the UITextView's scrollEnabled property set to YES in all cases. If it contains less text it will just not scroll. If you set the property while configuring the cell, you might get this kind of weird behavior, because UIKit is reusing those cells and probably the UITextView too.
For making the last line visible, I'm guessing you need to calculate the text view size more accurately. Try using the attributedText to set the text, with all the formatting you need. Then, in order to calculate the size, you can do it like this:
NSAttributedString *text = self.yourCellsTextView.attributedText;
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:text];
CGSize size = [calculationView sizeThatFits:CGSizeMake(self.yourCellsTextView.frame.size.width, FLT_MAX)];
CGFloat finalMessageHeight = size.height;
Hope this helps :).

iOS 7 autoresizing UITextView with AutoLayout breaks line randomly

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)).

how to make uitextview none scrollable but display all content

At the moment in my IB I have a View Controller which is covered by a UIScroll View. Within the scroll view I have a UIImageView at the top, a UILableView in the middile and a MKMapView at the bottom. The UILableView number of lines is set to 0 (infinite) and word wrap allowing me display as much content as I want.
I want to be able to tap telephone numbers and website url's for the content in my UILableView. The best way I've found so far is to change it to a UITextView which handles all of this for you. However... I can not get the same behaviour with the scrolling.
Before the image, label and map used to scroll as a block. Now, only the textView scrolls. Any advice appreciated.
the displaying part is correct, that is calculate the frame of the textView based on the size of its text for scrolling add this [textView setScrollEnabled:NO];
You can try using this project:
https://github.com/mattt/TTTAttributedLabel
label.dataDetectorTypes = NSTextCheckingTypeLink; // Automatically detect links when the label text is subsequently changed
label.delegate = self; // Delegate methods are called when the user taps on a link (see `TTTAttributedLabelDelegate` protocol)
label.text = #"Fork me on GitHub! (http://github.com/mattt/TTTAttributedLabel/)"; // Repository URL will be automatically detected and linked
NSRange range = [label.text rangeOfString:#"me"];
[label addLinkToURL:[NSURL URLWithString:#"http://github.com/mattt/"] withRange:range]; // Embedding a custom link in a substring

Resources