I have a slider that is functioning as 2 sliders, according to the audio played - when 1 type of audio (some kind of vocal guidance) is disabled, a music is played, and the slider controls the music's volume.
When changing roles, the slider changes positions, according the its role (guidance - upper in the view, music - lower) and adjusts the its value (the volume) to a saved volume value for that type of sound (guidance sound or music sound).
The type of effect I was looking for was -
Move the slider to its new location, using [UIView
animateWithDuration]
When the slider reaches its location, change its value to reflect the
volume, again, using [UIView animateWithDuration].
First, I wrote it like that -
[UIView animateWithDuration:0.3
animations:^{self.volumeSlider.frame = sliderFrame;}
completion:^(BOOL finished){
[UIView animateWithDuration:0.3
animations:^{self.volumeSlider.value = newValue;}
];
}];
Which worked very well in the iOS 6 simulator (using Xcode 4.6.3), but when changing to my phone, running iOS 7, the slider changed its position and then the slider's value jumped to the new value.
The same problem occurred again when running in the iOS 7 simulator that comes with Xcode 5, so I assume it's an iOS 7 problem.
I did some experiments, all with different results:
I tried setting the volume using '[UIView animateWithDuration:0.3 delay:0.3 options: animations: completion:]', meaning, not in the completion part, but the same thing happend.
When putting just the 2 animations one after another (each as separate animation, each without a delay, one after another) the result will vary according to the order of the animations.
[UIView animateWithDuration:0.3 animations:^{self.volumeSlider.value = newValue;}];
[UIView animateWithDuration:0.3 animations:^{self.volumeSlider.frame = sliderFrame;}];
Will move both the slider and its value together, both animated, while
[UIView animateWithDuration:0.3
animations:^{self.volumeSlider.frame = sliderFrame;}];
[UIView animateWithDuration:0.3
animations:^{self.volumeSlider.value = newValue;}];
Will move the slider's position, and then change its value without animation.
I tried calling the 2nd animation through
[self performSelector:#selector(animateVolume:) withObject:nil afterDelay:0.3];
And again - the slider moved, and then the value changed at once.
WHY, OH WHY??
If it helps, this is the description of the slider, BEFORE the first animation -
<UISlider: 0xcc860d0; frame = (23 156; 276 35); autoresize = RM+BM;
layer = <CALayer: 0xcc86a10>; value: 1.000000>
And After the first animation ended -
<UISlider: 0xcc860d0; frame = (23 78; 276 35); autoresize = RM+BM;
animations = { position=<CABasicAnimation: 0xbce5390>; };
layer = <CALayer: 0xcc86a10>; value: 0.000000>
Notice the animations section, that shouldn't be there by now (the description is logged from [self animateVolume] which is called with a delay of .3 seconds).
I know its a weird problem, but I would very much appreciate help with it.
Thank :)
Dan
UPDATE
As Christopher Mann suggested, changing the value in the UIView:animationWithDuration is not the official way of using it, and the correct way would be to use UISlider's setValue:animated.
However, for the people who will encounter such problem in the future - it seems that iOS 7 has some difficulties with that method, such that it is not animated in some cases (I think it will not be animated if the project was started in Xcode < 5).
The problem and its solution are described here.
My code that solved this problem is:
[UIView animateWithDuration:0.3
animations:^{self.volumeSlider.frame = sliderFrame;}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 animations:^{
[self.volumeSlider setValue:newValue animated:YES];
}];
currentSliderMode = mode;
}];
If you want to animate the change in slider value you should use setValue:animated: instead of setting .value directly. Changing volumeSlider.value inside the UIView animation block is likely interfering the the animation.
Related
I have a simple method for animate view.
-(void)animateSelf
{
CABasicAnimation * animation;
animation = [CABasicAnimation animationWithKeyPath:#"position.y"];
// settings ...
[self.view.layer addAnimation:animation forKey:#"position.y"];
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
// settings ...
[self.view.layer addAnimation:animation forKey:#"transform.rotation.z"];
[UIView animateWithDuration: 1.0 animations:^{
CGRect rect = self.view.frame;
rect.origin.y += 800;
self.view.frame = rect;
} completion:nil];
}
For iOS 7 it worked well. But for iOS 8 animation behaves unpredictably. Is there a way to combine these animations for iOS 8?
I tried to replace animateWithDuration: by another CABasicAnimation, but it did not help.
The view.frame logs are correct, but the animation of view.frame jumps out of obscure origin.
After removing CABasicAnimation for position.y (the first one) bug is gone.
You have three animations:
You animate the layer's position
You animate the layer's transform
You animate the view's frame which in turn animates the layer's position.
Animations 1 and 3 collide.
On iOS 7 the behavior is that animation 3 cancels animation 1.
On iOS 8 the behavior is that animations 1 and 3 run concurrently ("Additive Animations").
I suggest you just remove animation 1 and also check out the related WWDC 2014 video (I think it was Building Interruptible and Responsive Interactions).
What worked for me was disabling autolayouts on the view and THE SUBVIEWS of the view I was animating at some point before doing the animation. Disabling autolayouts from the storyboard was not enough in iOS8.
[viewToAnimate removeFromSuperview];
[viewToAnimate setTranslatesAutoresizingMaskIntoConstraints:YES];
//addSubview again at original index
[superView insertSubview:viewToAnimate atIndex:index];
This example might help you, I wish I had discovered it before wasting hours. Rather than animate the frame, animates its contraints. Click on the auto layout constraint you would like to adjust (in interface builder e.g top constraint). Next make this an IBOutlet;
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
Animate upwards;
self.topConstraint.constant = -100;
[self.viewToAnimate setNeedsUpdateConstraints];
[UIView animateWithDuration:1.5 animations:^{
[self.viewToAnimate layoutIfNeeded];
}];
Animate back to original place
self.topConstraint.constant = 0;
[self.viewToAnimate setNeedsUpdateConstraints];
[UIView animateWithDuration:1.5 animations:^{
[self.viewToAnimate layoutIfNeeded];
}];
Originally posted by me here
So you would adjust the constraint for self.view.frame in your example.
I also got an issue of little nasty differences between iOS7 and iOS8 animation.
In most cases it was broken it was either:
single combination of Scale, Transform and Rotate CGAffineTransforms - the result was dependant on iOS version
or complex sequence of animations on different views - some views were 'reseting' their positions before commencing a new piece of animations. About 5% of animation pieces were affected.
I'm pretty sure there were no simultaneous animations on the problematic views.
Autolayout and constraints suggestions did not help (moreover, all animated views were create in code as autolayout interfered with animation a lot even before iOS8).
What turned out to be a universal solution for both problems is to put the problematic views into a wrapper view and use it to split-off Rotation animation or to do the animation that causes 'reset' effect. Now it functions the same in 7.1.1 and 8.1.1.
I'm trying to move 2 views together :
[UIView animateWithDuration:0.5
animations:^{
[currentView setCenter:CGPointMake(currentView.center.x + currentView.frame.size.width, currentView.center.y)];
[nextView setCenter:CGPointMake(nextView.center.x + nextView.frame.size.width, nextView.center.y)];
}];
However, I can't manage to do it. Only the nextView is moving.
If I just commit the animation on just one, it works fine.
What could be the reason?
Could it be because of my declaration :
nextView = [[UIView alloc] initWithFrame:currentView.frame];
If I read this correct you have 2 views with the exact same frame, being animated in the exact same way. Could it be that they are both moving but one is on top of the other so you only see the top view?
I've found a solution to my problem.
The main difference between my two view is that one is created from a .xib, the other programmatically.
And everything work by writing :
[currentView setTranslatesAutoresizingMaskIntoConstraints:YES];
So is there something to change in the .xib file, so I would not have to set this mask programmatically.
I've implemented some code, which is working nicely, to scroll a UIScrollview automatically based on a timer being triggered.
Here it is:
....
CGPoint offset;
....
offset = scroller.contentOffset;
....
- (void) scrollWords: (NSTimer *) theTimer
{
offset.y = offset.y+300;
[UIScrollView beginAnimations:#"scrollAnimation" context:nil];
[UIScrollView setAnimationDuration:50.0f];
[scroller setContentOffset:offset];
[UIScrollView commitAnimations];
}
However, I've noticed that the while the scrolling is happening, the scroll rate varies; half way through it scrolls 2 or 3 lines of text per second, but at the start and finish it's much slower, perhaps only 0.5 lines per second. Is there any way of controlling the scroll rate?
Thanks in advance.
Paul.
You're looking for setAnimationCurve:. Specifically what you're describing is the effect of UIViewAnimationCurveEaseInOut. Try adding [UIScrollView setAnimationCurve:UIAnimationCurveLinear];
Also, you're using old style animation code. If you're targeting iOS 4 or above, check out this new style that's much more friendly (in my opinion):
- (void) scrollWords: (NSTimer *) theTimer
{
offset.y = offset.y+300;
[UIScrollView animateWithDuration:50.0f delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
[scroller setContentOffset:offset];
}];
}
Using the delay parameter, you can probably even get rid of your NSTimer. With this code you can scroll the table view after 5 seconds.
- (void) scrollWordsLater
{
offset.y = offset.y+300;
[UIScrollView animateWithDuration:50.0f delay:5.0 options:UIViewAnimationOptionCurveLinear animations:^{
[scroller setContentOffset:offset];
}];
}
I have a small animation at the begining of my app, two doors open up horizontally, top and bottom to reveal screen. All good so far, the top section moves off fine, however as i have a scroll view the page is longer than standard, 175000 to be exact. Im usure of how to modify the code to send the bottom png off the screen as at the moment its stuck going to where the standard screen would end. Could someone advise? Ive tried so far manually adding the height after self.view.size.height; but that didn't work, im assuming its around there somewhere I should be altering
CGRect doortopFrame = doortop.frame;
doortopFrame.origin.y = -doortopFrame.size.height;
CGRect doorbottomFrame = doorbottom.frame;
doorbottomFrame.origin.y = self.view.bounds.size.height;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:2.0];
[UIView setAnimationDelay:0.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
doortop.frame = doortopFrame;
doorbottom.frame = doorbottomFrame;
[UIView commitAnimations];
Fixed this, changing bottom animation to origin.X does the trick
I'm hoping someone can help. I have a fairly standard UIView animation where the only real animation is a CGAffineTransformMakeScale that is set on a loop. I want to have two buttons, one that increases the rate (or more accurately reduces the animation duration) of scale and one that decreases the rate (or more accurately increases the animation duration) of the scale.
Is this even possible? I apologise if this is obvious but I am a novice and looking for advice - even if it's directed reading. Let me know if I should supply any further info to help.
Many thanks in advance!
I don't know of a way to modify an in-progress animations.
Here's code that I think does what you want. It uses the increase/decrease buttons to change an ivar animationDuration, and manually loops the animation as two halves (a forward half and a reverse half). Each time the new animation (forwards or reverse) starts, it gets the value for animationDuration at that point in time, so for short animations it will appear to change pretty much instantly. It would not work well for long duration animations however, as it actually only changes the animation speed at the max/min points of the animation.
You could break the animation up even smaller if you wanted to update more frequently - e.g. split it into 4 instead of 2 (1 -> 1.5, 1.5 -> 2.0, 2.0 -> 1.5, 1.5 -> 1.0) or even more if you need it.
Header (MyViewController.h):
// Define some constants to be edited to suit our needs
#define kAnimationDurationMin 0.1
#define kAnimationDurationDefault 0.4
#define kAnimationDurationMax 2.0
#define kAnimationDurationStep 0.05
#interface MyViewController : UIViewController {
// The variable to store the target animation duration in
double animationDuration;
// Whether the next animation should be a reverse animation or not
BOOL reverse;
// The view to be animated, this should be connected in Interface Builder
IBOutlet UIView *ball;
}
//The method to actually do the animation
-(void)doAnimation;
// These 2 methods should be connected to the 'touch up inside' event of the relevant buttons in Interface Builder
- (IBAction)incButtonPressed:(id)sender;
- (IBAction)decButtonPressed:(id)sender;
#end
Implementation (MyViewController.m):
#import "MyViewController.h"
#implementation MyViewController
-(void)viewDidLoad {
// If animation duration has not yet been set (it will be zero) then set it to the default.
if (animationDuration < kAnimationDurationMin) animationDuration = kAnimationDurationDefault;
// Start the animation
[self doAnimation];
}
// This method does the animation
-(void)doAnimation {
[UIView beginAnimations:#"ball" context:NULL];
[UIView setAnimationDuration: animationDuration];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
// Do not repeat
[UIView setAnimationRepeatCount: 0];
// Not autoreversing allows us to trigger the animationDuration change twice as frequently.
[UIView setAnimationRepeatAutoreverses:NO];
// When the animation is complete, start it again by calling [self doAnimation];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(doAnimation)];
if (reverse) {
// Reset to default
ball.transform = CGAffineTransformMakeScale(1,1);
} else {
// Target of forward animation
ball.transform = CGAffineTransformMakeScale(2,2);
}
// Toggle reverse
reverse = !reverse;
[UIView commitAnimations];
}
- (IBAction)incButtonPressed:(id)sender {
animationDuration += kAnimationDurationStep;
if (animationDuration > kAnimationDurationMax) animationDuration = kAnimationDurationMax;
}
- (IBAction)decButtonPressed:(id)sender {
animationDuration -= kAnimationDurationStep;
if (animationDuration < kAnimationDurationMin) animationDuration = kAnimationDurationMin;
}
#end
You want setAnimationDuration of the UIView class:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.4];
view.transform = CGAffineTransformMakeScale(0.5, 0.5);
[UIView commitAnimations];
Here's the relevant documentation:
https://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW62