How to properly animate UIScrollView contentOffset
Did you try the same approach, but with disabled animation in scrollViewDidScroll
?
On iOS 7, you could try wrapping your code in scrollViewDidScroll
in
[UIView performWithoutAnimation:^{
//Your code here
}];
on previous iOS versions, you could try:
[CATransaction begin];
[CATransaction setDisableActions:YES];
//Your code here
[CATransaction commit];
Update:
Unfortunately that's where you hit the tough part of the whole thing. setContentOffset:
calls the delegate just once, it's equivalent to setContentOffset:animated:NO
, which again calls it just once.
setContentOffset:animated:YES
calls the delegate as the animation changes the bounds of the scrollview and you want that, but you don't want the provided animation, so the only way around this that I can come up with is to gradually change the contentOffset
of the scrollview, so that the animation system doesn't just jump to the final value, as is the case at the moment.
To do that you can look at keyframe animations, like so for iOS 7:
[UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
[self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)];
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
[self setContentOffset:CGPointMake(index*elementWidth, 0)];
}];
} completion:^(BOOL finished) {
//Completion Block
}];
This will get you two updates and of course you could use some math and a loop to add up a lot more of these with the appropriate timings.
On previous iOS versions, you'll have to drop to CoreAnimation for keyframe animations, but it's basically the same thing with a bit different syntax.
Method 2:
You can try polling the presentationLayer of the scrollview for any changes with a timer that you start at the beginning of the animation, since unfortunately the presentationLayer's properties aren't KVO observable. Or you can use needsDisplayForKey
in a subclass of the layer to get notified when the bounds change, but that'll require some work to set up and it does cause redrawing, which might affect performance.
Method 3:
Would be to dissect exactly what happens to the scrollView when animated is YES try and intercept the animation that gets set on the scrollview and change its parameters, but since this would be the most hacky, breakable due to Apple's changes and trickiest method, I won't go into it.
Smooth move of the contentoffset UIScrollView Swift
You can use UIView.animations
func goToPoint() {
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(2, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}
}
Swift version
DispatchQueue.main.async {
UIView.animate(withDuration: 0.2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.myScrollView.contentOffset.x = CGFloat(startingPointForView)
}, completion: nil)
}
Swift 4
DispatchQueue.main.async {
UIView.animate(withDuration: 1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}
Can I animate the UIScrollView contentOffset property via its layer?
You have to animate the bounds
property. In fact, that's what the contentOffset
property uses behind the scenes.
Example:
CGRect bounds = scrollView.bounds;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
animation.duration = 1.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = [NSValue valueWithCGRect:bounds];
bounds.origin.x += 200;
animation.toValue = [NSValue valueWithCGRect:bounds];
[scrollView.layer addAnimation:animation forKey:@"bounds"];
scrollView.bounds = bounds;
If you're curious, the way I used to get this information is the following:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[scrollView setContentOffset:CGPointMake(200, 0) animated:NO];
[UIView commitAnimations];
NSLog(@"%@",scrollView);
The NSLog
call will output:
<UIScrollView: 0x860ba20; frame = (-65.5 0; 451 367); clipsToBounds = YES; autoresize = W+RM+TM+H; animations = { bounds=<CABasicAnimation: 0xec1e7c0>; }; layer = <CALayer: 0x860bbc0>; contentOffset: {246, 0}>
The animations
snippet will list all the active animations, in this case { bounds=<CABasicAnimation: 0xec1e7c0>; }
.
Hope this helps.
UIScrollView animation of height and contentOffset jumps content from bottom
It might be a bug in UIKit. It happens when there's a simultaneous change of size
and contentOffset
of UIScrollView
. It'd be interesting to test if this behavior also happens without Auto Layout.
I've found two workarounds to this problem.
Using contentInset (the Messages approach)
As it can be seen in the Messages app, UIScrollView
's height doesn't change when a keyboard is shown - messages are visible under the keyboard. You can do it the same way. Remove constraint between UICollectionView
and the view that contains UITextField
and UIButton
(I'll call it messageComposeView
). Then add constraint between UICollectionView
and Bottom Layout Guide
. Keep the constraint between messageComposeView
and the Bottom Layout Guide
. Then use contentInset
to keep the last element of the UICollectionView
visually above the keyboard. I did it the following way:
- (void)updateKeyboardConstraint:(CGFloat)height animationDuration:(NSTimeInterval)duration {
self.bottomSpaceConstraint.constant = height;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
CGPoint bottomOffset = CGPointMake(0, self.collectionView.contentSize.height - (self.collectionView.bounds.size.height - height));
[self.collectionView setContentOffset:bottomOffset animated:YES];
[self.collectionView setContentInset:UIEdgeInsetsMake(0, 0, height, 0)];
[self.view layoutIfNeeded];
} completion:nil];
}
Here self.bottomSpaceConstraint
is a constraint between messageComposeView
and Bottom Layout Guide
. Here's the video showing how it works.
UPDATE 1: Here's my project's source on GitHub. This project is a little simplified. I should've taken into consideration options passed in the notification in - (void)keyboardWillShow:(NSNotification *)notif
.
Performing changes in a queue
Not an exact solution, but scrolling works fine if you move it to the completion block:
} completion:^(BOOL finished) {
[self.collectionView setContentOffset:CGPointMake(0, self.collectionView.contentSize.height - self.collectionView.bounds.size.height) animated:YES];
}];
It takes 0.25s for the keyboard to show, so the difference between the beginnings of the animations might be noticeable. Animations can also be done in the reversed order.
UPDATE 2: I've also noticed that OP's code works fine with this change:
CGPoint bottomOffset = CGPointMake(0, self.collectionView.contentSize.height - (self.collectionView.bounds.size.height - height));
but only when contentSize
's height
is less than some fixed value (in my case around 800
, but my layout may be a little different).
In the end I think that the approach I presented in Using contentInset (the Messages approach)
is better than resizing UICollectionView
. When using contentInset
we also get the visibility of the elements under the keyboard. It certainly fits the iOS 7 style better.
UIScrollView setContentOffset:animated not working in iOS11
try this
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:.25 animations:^{
[self setContentOffset:CGPointZero animated:YES];
}];
});
Change the speed of setContentOffset:animated:?
There is no a direct way of doing this, nor doing the way you wrote it. The only way I can accomplish this is by making the movement/animation by my own.
For example move 1px every 1/10 second should simulate a very slow scroll animation. (Since its a linear animation maths are very easy!)
If you want to get more realistic or fancy and simulate easy-in easy-off effect then you need some maths to calculate a bezier path so you can know the exact position at every 1/10 second, for example
At least the first approach shouldn't be that difficult.
Just use or -performSelector:withObject:afterDelay or NSTimers
with
-[UIScrollView setContentOffset:(CGPoint*)];`
Hope it helps
Related Topics
How Do Set a Width and Height of an Image in Swift
It Gives Errors When Using Swift Static Library with Objective-C Project
Keep Getting Error Messages When Compiling Newest Version of Admob on iOS6 Sdk
How to Activate Tcp Keepalive on Apple iOS Devices
How Big Can the Payload Be When Sending Data via Watchconnectivity
Programmatically Creating Uilabel
Using Apple's Reachability Class in Swift
How to Restrict the iOS App Only for iPhone Excluding iPad
Observing Change in Frame of a Uiview During Animation
Use Logical Operator as Combine Closure in Reduce
Where to Highlight Uicollectionviewcell: Delegate or Cell
How Should a Swift + Objective-C Project Be Setup for Unit Testing
Wait for Swift Animation to Complete Before Executing Code
How to Upload Video to Server from Iphone
Best Way to Save and Retrieve Uicolors to Core Data
_Unused Flag Behavior/Usage (Gcc with Objective-C)