iOS8 - keyboard input accessory view with dynamic height - ios

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.

Related

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

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.

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;
}

Can't scroll both UIScrollView in UITableViewCell and the UITableView itself

My pure AutoLayout UITableViewCell looks like this in Interface Builder:
UITableViewCell
|-> UITableViewCell.contentView
|-> UIView (ScrollViewContainerView)
|-> UIScrollView
|-> left (fixed)
|-> center (fill remaining)
|-> right (fixed)
The UIScrollView contains a left, center, and right UIView. left and right are both fixed width, while center expands to fill the remainder of the UIView. The UIScrollView constraints are to align all edges to ScrollViewContainerView. ScrollViewContainerView constraints are to align all edges to the UITableViewCell.contentView. I have a constraint on center's width to be a multiple of ScrollViewContainerView's width, so the UIScrollView scrolls left and right, but the height is fixed and does not scroll. Note that the UIScrollView has been subclassed to include this code so that the UITableView can detect a tap on the cell to toggle selection.
The issue is that I currently can either scroll the UITableView containing these UITableViewCells up and down or I can scroll the UIScrollViews in the UITableViewCells left and right, not both.
When ScrollViewContainerView.userInteractionEnabled == YES, I can't scroll the UITableView up and down, but I can scroll the UIScrollView left and right. When ScrollViewContainerView.userInteractionEnabled == NO, I can scroll the UITableView up and down, but I can't scroll the UIScrollView left and right. userInteractionEnabled == YES on everything else in the above hierarchy.
I can get away with having ScrollViewContainerView as a sibling view to the UIScrollView (making the UIScrollView the direct descent of contentView -- can't get rid of this view completely, because I require it to get the dimensions for the UIScrollView frame). In that case, the opposite handling with userInteractionEnabled holds.
I know I've done this before in other projects before, but starting fresh again, I can't seem to figure out what step I'm missing. Currently using Xcode 6 6A215l targeting iOS 8, though I have reproduced the issue under Xcode 5 targeting iOS 7.
It sounds like the scrollview is causing your tableview to not allow userInteraction when being scrolled. I'm sure that if you called - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView in the UIScrollView delegate (not sure for iOS 8), but you could just do
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
if(scrollView.dragging == YES) {
self.<scrollViewName>.userInteractionEnabled = NO;
}
}
This is untested code, but it's just a bit of help to get you where you need to go.
Hope it helps!
I met some similar problem.
I have a scrollView in tableViewCell. All works fine.
Until one day, someone told me that the tableView can't scroll up/down when finger is touched on the scrollView in 6p. Just in 6p, not in 5, 5s,or6.
This makes me almost crazy.
Finally, I set the scrollView's height smaller than the height in storyboard.
Biu ~ It works~~~
Still, I don't know why.
#user2277872's answer put me on the right track to look at the output of the UIScrollView delegate methods of the UIScrollView in my UITableViewCell subclass. Putting an NSLog() in scrollViewWillBeginDragging: made me notice that the UIScrollView was receiving scrolling events while I was trying to scroll the UITableView. My UIScrollView had a contentSize larger than its frame in both directions, but I've forced that view to only scroll horizontal, so ignored the height and reset it. That force was my undoing and I should have known it at the time -- the correct solution is to fix the frame height. If the UIScrollView doesn't think there is more vertical content, it will correctly forward the swipe up/down gesture to the UITableView.
While I attempt to figure out why my contentSize is too large when it wasn't before (thinking I'm missing a clipToBounds somewhere), what I'm doing to force horizontal scrolling temporarily is (in the UITableViewCell's subclass):
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGSize contentSize = self.scrollView.contentSize;
contentSize.height = self.frame.size.height;
self.scrollView.contentSize = contentSize;
}
EDIT: Actually, this is seemingly better than overriding drawRect. This would be in the UIScrollView subclass:
/*
* Lock to horizontal scrolling only.
*/
- (void)setContentSize:(CGSize)contentSize
{
[super setContentSize:CGSizeMake(contentSize.width, 1)];
}
The height struct member isn't too important, as long as it's guaranteed to be smaller than the frame.size.height of the UITableViewCell. Still hacky, still need to find why I could clip before and not now.

UITableView Header Gap and Resizing Programmatically

I have two questions related to UITableViews.
1) The first one is, what is the gap at the top of the UITableView? My app always starts with the top cell not flush with the top of the tableview (as shown in the second image), it starts about one cell lower, i.e. where that gap is in the interface builder. I can't find where that is coming from, or why.
2) I can't seem to resize the uitableview programmatically, I'm trying to reduce the height after a popup appears. I've shown an example of it not working in the second picture.
Here is (an example of) what I am trying at the moment:
- (void)viewDidLoad
{
[super viewDidLoad];
self.table_view.delegate = self;
CGRect tableBounds = self.table_view.bounds;
tableBounds.size.height -= 100;
self.table_view.bounds = tableBounds;
CGRect tableFrame = self.table_view.frame;
tableBounds.size.height -= 100;
self.table_view.frame = tableFrame;
}
Thanks!
UITableView Selected:
Simulation:
In your xib (or storyboard) put your UITableView at position (0,0). ( the same position as the navigation bar).
The first image shows that your table view has problems even in Interface Builder. It looks as if you've set the top inset incorrectly; check your edge insets.
The reason your resizing code is not working is probably that it is too early (viewDidLoad); put it in viewDidAppear: and see if that works, and if it does, try moving it back to viewWillAppear: so the user doesn't see the resizing. If it doesn't work, it might be because you're using auto layout; you can't manually alter the frame of something whose frame is dictated by auto layout. (Your resizing code is also silly; you want to set the frame, not the bounds.) But it might also be because you're using a UITableViewController in a UINavigationController; if you do that, the table view is under the navigation controller's direct control and its size is not up to you.

UIScrollView scrolled after setContentSize

So I want to create some detail content in UIView(320,470) that taller than Viewport (320, 367).
I create it separated in IB like (see pic.). Everything looks OK until I setContentSize to make the UIScrollView scrollable..
This is my code placed in ViewDidLoad
CGRect frame = self.uiContent.frame;
[self.uiScrollView addSubview:self.uiContent];
[self.uiScrollView setContentSize:frame.size];
The content is scrolled with animation to middle after setContentSize is called.. How to prevent that auto-scroll?
I found the culprit.. It was UITextView. Sorry if I don't mention I use UITextView for multiline label under address label.
Quoting "Taketo Sano" on other question : https://stackoverflow.com/a/5673026/453407
I've investigated how the auto-scroll is done by tracking the
call-trace, and found that an internal [UIFieldEditor
scrollSelectionToVisible] is called when a letter is typed into the
UITextField. This method seems to act on the UIScrollView of the
nearest ancestor of the UITextField.
UIScrollView is auto scrolled to UITextView when UITextView text is changed. So I found the solution by subclassing UIScrollview and override
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated {
and return nothing to disable the auto scroll... If you plan to use it in future, just use a bool variable to enable / disable it by using
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated {
if (!self.disableAutoScroll) {
[super scrollRectToVisible:rect animated:animated];
}
}
so you can disable the autoscroll before you change the UITextView by code and enable it after.

Resources