UIScrollView's content offset being reset - ios

I have been struggling for a long time with a problem with a UIScrollView.
I have a UIScrollVIew that contains a UITextView as a subview. When I select the text view a keyboard pops up. I want to resize the text view to fit exactly in the available space, and also scroll the scroll view so that the text view is positioned exactly in the visible space (not hidden by the keyboard).
When the keyboard appears I call a method that calculates the appropriate size for the text view and then performs the following code:
[UIView animateWithDuration:0.3
animations:^
{
self.textView.frame = frame;
}
];
[self.scrollView setContentOffset:CGPointMake(0,frame.origin.y) animated:YES];
(here frame is the appropriate frame for the text view).
Unfortunately the scroll view does not always scroll to the correct position, especially when it is already at a non zero vertical content offset when I select the text view. I know that the content offset that I'm setting it to is correct.
After a lot of testing I finally realized that what was happening was that after the animation completed, the scroll view was automatically scrolling again.
This code does work:
UIView animateWithDuration:0.3
animations:^
{
self.textView.frame = frame;
}
completion:^(BOOL finished) {
[self.scrollView setContentOffset:CGPointMake(0, frame.origin.y) animated:YES];
}
];
but it looks strange because the scroll view scrolls to the wrong position, then to the right one.
Does anyone know how I can prevent the scroll view from changing it's content offset when the text view frame finishes its animation?
I am testing using iOS 5.0.
Here is a solution that I found that works. I'm still don't completely understand what's happening, possible it has something to do with the way my springs and struts are set. Basically I am shrinking the scroll view content size by the same amount that the text view shrinks.
- (void)keyboardDidShow:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
// Get the height of the keyboard
CGRect kbRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
kbRect = [self.view convertRect:kbRect fromView:nil];
CGSize kbSize = kbRect.size;
// Adjust the height of the text view to fit in the visible view
CGRect frame = self.textView.frame;
int visibleHeight = self.view.frame.size.height;
visibleHeight -= kbSize.height;
frame.size.height = visibleHeight;
// Get the new scroll view content size
CGSize contentSize = self.scrollView.contentSize;
contentSize.height = contentSize.height - self.textView.frame.size.height + frame.size.height;
[UIView animateWithDuration:0.1
animations:^
{
self.textView.frame = frame;
// Note that the scroll view content size needs to be reset, or the scroll view
// may automatically scroll to a new position after the animation is complete.
self.scrollView.contentSize = contentSize;
[self.scrollView setContentOffset:CGPointMake(0, frame.origin.y) animated:YES];
}
];
// Turn off scrolling in scroll view
self.scrollView.scrollEnabled = NO;
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
// Update the view layout
[UIView animateWithDuration:0.3 animations:^
{
self.scrollView.contentOffset = CGPointZero;
[self updateViewLayout];
}
];
// Turn on scrolling in the scroll view
self.scrollView.scrollEnabled = YES;
}
([self updateViewLayout] is a method that returns the text view to the correct height, and resets the scroll view content size, as well as making sure all the other subviews are properly positioned).

Do not adjust the frame of the text view. All you need to do is scroll the scrollview. If you scroll the scrollview animated, then the textView will slide up animated. Just do the following:
[self.scrollView setContentOffset:CGPointMake(0, frame.origin.y) animated:YES];
If you need the scroll view to scroll at a specific rate then manually adjust the content offset in an animation block.(I haven't tested this, you may want to adjust the animation flag, or step through the content offset one pixel at a time if this doesn't work)
[UIView animateWithDuration:0.3f
animations:^{
[self.scrollView setContentOffset:CGPointMake(0, frame.origin.y) animated:YES];
} ];

I believe I also had something similar. I wanted my horizontal UIScrollView to go back to it's original offset (0,0), and every time I animated back to it, it would be just a couple pixels off, but as soon as someone touched the scrollview, it would continue and finish the animation (almost like lag in a video game).
Due to that, I tried putting it in GCD as the main queue and it worked perfectly fine after that. Here's the code:
- (void)setPageZero{
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:1.0f
delay:0 options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[self.scrollView setContentOffset:CGPointMake(0, self.scrollView.frame.origin.y) animated:YES];
}
completion:^(BOOL finished) {}];
});
}
What that basically does, is that it puts the animation into the main queue (kind of like a superhighway in the CPU/GPU) and it prioritizes it over other things!
Hope this helps future readers!

I'm having a similar problem. Basically I have a scroll view with three pages each containing a custom UIView class. The custom UIView class contains an image and a number of overlay views containing information about the image. The overlay views contain a number of UITextView fields.
I attach the custom views to the scroll view and then I load the image & overlays using block operations. I swipe to the right by deleting the first view, repositioning the next two views, and adding an new view to the right.
If I create the UITextField objects and disable scrolling as follows:
UITextView* view = [[UITextView alloc]
initWithFrame:CGRectMake(position.x,position.y,width,height)];
view.editable = NO;
view.scrollEnabled = NO;
Then the scroll view gets repositioned between pages when I return from the block code. Note that I don't reposition the scroll view and on exit from the block, the scroll view is in the correct position.
However, if I comment out the code to disable scrolling
UITextView* view = [[UITextView alloc]
initWithFrame:CGRectMake(position.x,position.y,width,height)];
view.editable = NO;
// view.scrollEnabled = NO;
this repositioning does not occur and the scrolling works as expected.

Complete hack: May improve the reset issue:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[self setContentOffset:self.contentOffset animated:NO];
}

Related

Reduce view and subviews animated

I am currently trying to make a UIView containing some UILabel animate to a new size. But doing so I am having some trouble understanding what is really happening with my view. I read some other post about it but I am still unclear about what is really going on.
In my button I added something that just double the size of the right constraint :
[superView layoutIfNeeded];
rightConst.constant *= 2;
[UIView animateWithDuration:3
animations:^{
[superView layoutIfNeeded];
} completion:nil];
superView Being the view I wanna animate and rightConst the constraint to the right.
But doing so, the animation starts but it is actually coming from left. I don't understand this part. My goal would be to animate just the right side of the view to show the resize and maybe the bottom part of the view but the top left should be fixed.
Thanks.
As described in this document, if you call [aView layoutSubviews], layout of the subviews of aView is forced but layout of aView itself is NOT forced.
You need to call layoutSubviews of the superview of the view you want to animate. In this case, the superview of the superview of the two labels.
Solution is here
UIView *theView;
// theView is the superview of the superview of the two labels.
theView = superView.superview;
[theView layoutIfNeeded];
rightConst.constant *= 2;
[UIView animateWithDuration:3
animations:^{
[theView layoutIfNeeded];
} completion:nil];

Animate view out of screen goes wrong ios

I have a view controller that contains two views. What I need is that after the view controller has been pushed, the view on top (_sidepanelview) moves to the left disappearing of the screen.
Here is what I wrote:
-(void)viewDidAppear:(BOOL)animated{
CGRect newsidepanelviewposition = _sidepanelview.frame;
newsidepanelviewposition.origin.x = -_sidepanelview.frame.size.width;
[UIView animateWithDuration:0.5 delay:1.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
_sidepanelview.frame = newsidepanelviewposition;
} completion:^(BOOL finished) {
NSLog(#"Done!");
}];
}
The problem is that when I run the app, after the view controller been pushed, the view _sidepanelview disappears from the screen and appears again to the center of the screen coming from the right side instead of moving to the left from x=0 to x= -_sidepanelview width disappearing from the screen.
What I'm doing wrong?
Note: I did the app with auto layout unchecked and the animation worked fine, but using it with the auto layout turned on it freaks out!
As your note suggests, Auto Layout is the "culprit".
Your views have constraints that are periodically enforced by the AutoLayout engine. When you alter the frame of a view, the frame changes, but not the view's layout constraints. When the next cycle of layoutSubviews occurs, your views position and size (i.e. frame) will be reset to what the constraints dictate.
If you want to include AutoLayout for that view/viewcontroller, use constraint changes to perform the animation.
The golden rule here is, setFrame is the antithesis of AutoLayout.
Sample code
-(void)viewDidAppear:(BOOL)animated {
_sidePanelLeadingSpace.constant = -_sidepanelview.frame.size.width; // 1
[UIView animateWithDuration:0.5 delay:1.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
[self.view layoutIfNeeded]; // 2
} completion:^(BOOL finished) {
NSLog(#"Done!");
}];
}
Have a reference, _sidePanelLeadingSpace (NSLayoutConstraint), to the leading space constraint of _sidepanelview, for e.g., as an IBOutlet.
Alter the constraint's constant and layout the view by calling layoutIfNeeded in an animation block

How to have UIScrollView animate off screen when dragged

So I have a UIViewController and I have a UIScrollView inside of it, which has some UI elements.
I want the user to be able to drag the UIScrollView up and down, but when it drags past a certain threshold, I want it to animate off the screen. There needs to be some springiness to it though. I tried using -(void)scrollViewDidScroll: but couldn't seem to get it right. Any thoughts?
In scroll view delegate method -(void)scrollViewDidScroll:(UIScrollView *)scrollView; observe scrollView.contentOffset.y and trigger animation when it reaches expected y position.
for example:
CGFloat yTranslation = -200.f; // Value you want to shift
[UIView animateWithDuration:0.35 animations:^{
scrollView.transform = CGAffineTransformMakeTranslation(0, yTranslation);
} completion:^(BOOL finished) {
// code after completion
}];

UITableView not scrolling enough after expanding subview

I have a UITableViewController with a UIView at the bottom. (using storyboard). My UIView is set to hidden and changes state afterwards on click of button. First, I was trying to resize (increase height basically) my UIView on IBAction(buttonClick) using self.concluidoView.frame = CGRectMake(0, 0, 320, 50); but UIView was disappearing instead.
Now, it is correctly expanding UIView with the following code inside IBAction:
[UIView animateWithDuration:1.0
animations:^{
CGRect frame = self.concluidoView.frame;
frame.size.height += 100.0;
self.concluidoView.frame = frame;
}
completion:^(BOOL finished){
// complete
}];
The problem is that my UITableViewController is not scrolling enough to the new size, since the UIView is the last item in my tableView.
I've tried a few things to solve this problem, including manually trying to resize tableview to increase it's height to the same value of the UIViewincrease. I used the following code:
CGRect tableFrame = self.tableview.frame;
tableFrame.size.height += 100;
self.tableView.frame = tableFrame;
[self.tableview layoutIfNeeded];
The scrolling capacity of my tableviewwas actually smaller after this code. I would like to resize tableviewand allow scrolling, either manually, since I know how much my subviewwill increase, or automatically.
If you change the UITableView's frame, all you'll do is make it extend off screen most likely. What you want to do is get the table view to recognize that the UIView is larger. I'm assuming this UIView is the tableFooterView of your UITableView. Try doing the following:
UIView *footerView = self.tableView.tableFooterView;
self.tableView.tableFooterView = nil;
self.tableView.tableFooterView = footerView;
That will force the UITableView to reexamine the size of the footer view. I've had to do this before when changing the size of a footer view before.

How to scroll back up with a uiscrollview?

Basically I have a uiscrollview. The uiscrollview scrolls down when i push a certain button. However once the content offset has been set it will not allow me to scroll the uiscrollview back to the to.
For example...
If I set the content off set to 100 and animated:yes. The scrollview will scroll to that position and allow me to continue to scroll down the page but when trying to scroll up you pull up and see the upper par of the view but it then bounces back down.
Here is my code:
Firstly when the button is pressed I call the void function:
[self downExecute:scroller];
Secondly I run the void statement which set's my content off set...
- (void)downExecute:(id)sender {
NSLog(#"scroll down");
CGFloat currentOffset = scroller.contentOffset.y;
CGFloat newOffset;
newOffset = currentOffset + 100;
[UIScrollView animateWithDuration:0.3 animations:^(void) {
[scroller setContentOffset:CGPointMake(0.0,newOffset)];
}completion:^(BOOL finished) {
}];
}
Any Help would be greatly appreciated...
If your intent is to allow normal scrolling after you've repositioned the content, you could use scrollRectToVisible:animated: instead of changing the content offset.
try something like this to allow scrolling:
scroller.alwaysBounceVertical = YES;
and to manage duration of scrolling use this:
scroller.viewForBaselineLayout.layer.speed = 1.0f; // 1.0f is default
[scroller setContentOffset:(some CGPoint) animated:YES];

Resources