I have a nib with a full screen textView. In viewDidLoad,
self.textView.attributedText = //some text;
Its as simple as this and works fine except for iphone landscape mode.
In landscape mode, when I navigate to this page, contentOffset.y of this textView is not initilized to zero. So, by default the scroll position is at the middle of the content (I expected this to be at the start of the content).
For ipad and iphone portrait mode, scroll position is at the start of the content (contentOffset.y is zero)
In iOS7, to scroll to the top, you must take the content insets into account. I also found it was necessary to wait for the next runloop iteration, as Altaveron says.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
_textView.contentOffset = CGPointMake(-_textView.contentInset.left, -_textView.contentInset.top);
});
In iOS 7, I was also having the same issue. UITextView's scroll starts in the middle in the landscape view. I have navigationBar at the top of the view controller. To solve this issue, I've set the contentOffset in viewDidLoad as follows:
[self.textView setContentOffset: CGPointMake(0,-200) animated:NO];
I've subtracted 200 to balance with my navigation bar. And it worked for me. Since it is set in viewDidLoad, y offset is not reset to 0 if the user changes orientation.
Hope it helps!
The following workaround helped me. setText calls UITextView setText method.
[self performSelector:#selector(setText) withObject:nil afterDelay:0.f];
On iOS8 or older version of iOS, using textView.contentOffset = CGPointZero.
But from iOS 9 i think we need to update it on viewDidLayoutSubviews()
My similar issue was solved with the following:
textView.contentOffset = CGPointMake(0.0, -50)
On certain devices, the textView would initially be slightly scrolled down. But I needed it to be scrolled to the top.
For some reason, setting it to CGPointZero did not have any affect. However, setting the offset y to a large negative number did the trick.
Related
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;
}
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.
In this following code example self.contentView refers to the UIScrollView in question.
// Scroll to bottom.
CGPoint bottomOffset = CGPointMake(0, self.contentView.contentSize.height -
self.contentView.bounds.size.height);
if (bottomOffset.y >= 0.0)
{
[self.contentView setContentOffset:bottomOffset animated:YES];
}
Oddly, in iOS 6 this works perfectly fine, but in iOS 7 the scroll view (assuming it has a contentSize that's vertically larger than it's frame.size.height) only scrolls to the very bottom of the bottom most subview added to the scroll view.
For example, if the following cases hold true:
self.contentView.frame.size.height == 50.0
self.contentView.contentSize.height == 100.0
aSubView.frame.origin.y == 50.0
aSubView.frame.size.height == 20.0
The scrolling code will only scroll until aSubView is visible; self.contentView.contentOffset.y == 20.0 rather than self.contentView.contentOffset.y == 50.0 which would be at the bottom of the entire scroll view.
This is (of course) occurs until programmatically another subview is added to self.contentView (via a user interaction), then everything corrects itself.
For clarity, I set breakpoints before and after the scrolling code to measure changes to self.contentView.contentOffset.
Other fun fact, if I remove animated and set the contentOffset directly it works as expected on iOS 7, but I'd prefer keeping the animation.
NOTE: Not using interface builder
Just one line ..you can scroll to bottom.. !
[yourScrollview scrollRectToVisible:CGRectMake(yourScrollview.contentSize.width - 1, yourScrollview.contentSize.height - 1, 1, 1) animated:YES];
So I figured out a pretty unsatisfying solution rather quickly by wrapping the call in an async dispatch block.
// Scroll to bottom.
CGPoint bottomOffset = CGPointMake(0, self.contentView.contentSize.height
- self.contentView.bounds.size.height);
if (bottomOffset.y >= 0.0)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.contentView setContentOffset:bottomOffset animated:YES];
});
}
If anyone understands what is really causing the problem and can provide a better solution I'd gladly accept that as the answer, but for everyone else dealing with the same issue hopefully this works for you as well.
Disabling "Adjust Scroll View Insets" solved this for me. (Xcode 6, iOS 8)
You can deselecting 'Use Autolayout' in the File Inspector pane of main view within the Scroll View.This may help u. UIScrollView doesn't scroll after upgrading to iOS7 / xcode 5
I have a UICollectionView that I am populating with several cells and a custom flow layout. This works great in portrait mode. When I rotate to landscape I encounter an issue where I can't swipe up/down on the right portion of the screen. It seems to be the right w-h pixels which are unresponsive to touch. The collection view does draw cells properly and everything else seems normal. Also, if I begin a swipe in the working zone and go diagonally into the unresponsive area, the drag continues to work.
When the device is rotated, I make the following calls:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
_currentIndex = self.assetsCollectionView.contentOffset.y / self.assetsCollectionView.bounds.size.height;
[self.assetsCollectionView.collectionViewLayout invalidateLayout];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
float y = _currentIndex * self.assetsCollectionView.bounds.size.height;
[self.assetsCollectionView setContentOffset:CGPointMake(0, y) animated:NO];
}
These look fine to me, and when I comment them out completely, I get the same behavior.
Other details: The UICollectionView is the only UI component in my View Controller, except for a small detail label which I'm sure is not the issue.
I'm switching between two subclasses of UICollectionViewFlowLayout so that the cells can expand to full screen and back, but I'm experiencing the same problem no matter which layout, or even before I swap them out. One more detail is that the fullscreen layout is pageEnabled while the smaller one is not.
The containing UIViewController is inside a UITabController.
One more note: I've double checked my layout constraints to ensure there isn't funny business there either.
Any ideas?
I had exactly the same problem. After spending an evening trying to figure out what was wrong I managed to solve it by adding these two lines in viewDidLoad method of the CollectionView view controller class. (I do not use autolayout)
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
I hope that this solves your issue too.
I have a UINavigationController in my app. The UINavigationBar is set to opaque and all the scroll views do not overlap underneath the bar.
In one view I have a UITableView. The frame of the UITableView is (0 0; 320 504) on my iPhone 5. i.e. the height is 568 - 64 (the height of the nav bar and status bar).
The contentInset of the UITableView is (0, 0, 0, 0). When the table view first loads the contentOffset is (0, 0).
This is fine. Works brilliantly.
I added a UIRefreshControl to the table view. This works a couple of times but then after a few times of doing pull to refresh then the content at the top gets "stuck" under the nav bar.
When this happens I inspect the contentInset and it is now (-60, 0, 0, 0).
Is there any way to stop the UIRefreshControl from changing the contentInset?
This is probably the reason why UIRefreshControl is currently only supported on UITableViewController, rather than by addition to any scrollview (which you can get away with, in many cases).
The refresh control does its magic by tinkering with the content insets of the scrollview - particularly when it ends refreshing. Unfortunately the view controller is also tinkering with the content insets of the scroll view to fit it under the translucent nav and status bars. Fun ensues. Is this also an issue on iOS 6 (or, "good old iOS6" as I called it when dealing with the same issue).
The quickest solution is probably to add your table view as a child UITableViewController instead of a simple subview. I think that UITableViewController manages the insets for you at the end of the refresh. If that doesn't work, I've got workarounds for this but it will have to wait until I get back in the office.
I will add this answer here in case any one has problems with UIRefreshControl by changing the control properties (attributed title, tint, etc...):
Don't mess with the UIRefreshControl on -viewDidLoad:, use -viewDidAppear: instead.
Reset your table view contentInset.
-(void)pullToRefresh
{
[self.tableView reloadData];
[self.refreshControl endRefreshing];
[self.tableView setContentInset:UIEdgeInsetsMake(0, 0, 0, 0)];
}
You need override setContentInset: in you UICollectionView
- (void)setContentInset:(UIEdgeInsets)contentInset {
if (self.tracking) {
CGFloat difference = contentInset.top - self.contentInset.top;
CGPoint translation = [self.panGestureRecognizer translationInView:self];
translation.y -= difference * 3.0 / 2.0;
[self.panGestureRecognizer setTranslation:translation inView:self];
}
[super setContentInset:contentInset];
}