I am using a UIScrollView with a list of buttons made to look much like a UIPickerView. I have just implemented shake to shuffle, where upon detection of a shake I set the content offset of the UIScrollView to the position of the randomShuffle using the following.
[Singlescroll setContentOffset:CGPointMake(jumpX, jumpY+randShuffle*sepValue) animated:YES];
Instead of just moving the content offset to the random position that matches a button I would like to implement a shuffle animation where the view almost 'spins' like a slot machine and then ends up on the randomShuffle button position.
I tried to do this by simply animating the offset to the top of the UIScrollView then back down again before going back to the randomShuffle position (one after another), however this didn't work and it just went straight to the randomShuffle position. I realise that I didn't try this with a timer so the animations were not delayed however I would like to avoid running a timer if possible.
Is there any inbuilt animations that can handle this? and if not please can you suggest how I might approach this before using timers? thank you.
Instead of just using animated:YES, did you try putting it into something like:
[UIView animateWithDuration:.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[Singlescroll setContentOffset:maximumOffsetPoint]; //spin all the way up?
}completion:^(BOOL finished){
if (finished)
//kick off another animation, to spin it back down:
[UIView animateWithDuration:.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[Singlescroll setContentOffset:CGPointMake(jumpX, jumpY+randShuffle*sepValue)];
}completion:nil];
}
}];
I don't know if this answers your question, but completion blocks are super handy and might be what you need. Within the completion block, the second animation will only get called at the end of the first animation.
Also, there are several animateWithDuration: methods on UIView, but in my experience this one is more reliable for animating properties such as contentOffset and zoomToRect of scroll views.
Related
We have this animation for some bubble effect on a button , but it prevent him from being clickable. if we disable this animation he works.
Can we fix it to let it work with the animation ?
CGRect initi=[[initialPositions objectAtIndex:b.tag] CGRectValue];
int r1=arc4random()%4;
int r2=arc4random()%4;
initi.origin.x+=r1;
initi.origin.y+=r2;
[UIView animateWithDuration:0.8 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^
{
b.frame=initi;
}
completion:^(BOOL completed){ }];
As far as I know, the button is not actually disabled or unclickable during the animation. When the animation starts the button's touch area is immediately moved to the position where the button will be when the animation ends. This makes it feel like the button were unclickable during the animation.
From touch logic's point of view, the button is just moved from A to B and animation between A and B is just eye candy. This explains why touching doesn't work between the points.
The only working solution to this is to roll your own animation routine. If you want to take this route, this post called The Holy Grail of iOS Animation Intervals? by Ben Bojko should point you to the right direction.
As Markus says in his answer (voted) the button actually moves to it's end position at the beginning of the animation, and will accept clicks there as soon as the animation starts. The animation actually takes place in view hierarchy's "presentation layer".
You can use Markus' suggestion of doing your own animation, but that is processor-intensive and not as smooth as layer-based animation (which is how UIView animation works under the covers.)
If you want to use UIView animation and have your button respond to clicks while the animation is in-flight then you need to make the superview that bounds the entire animation clickable, and implement hit testing on the button's presentation layer to figure out if the tap hits the button or not. (the superview takes the tap, then checks it's coordinates to see if it is inside the buttons' presentation layer using the hitTest: method.)
I have a project on github called iOS CAAnimationGroup demo (link) that shows how to do this, both for CAAnimation based animation and UIView-based animation.
UI animations are great, very easy to use, and are used allot. The only one problem I have with it is that while the animation is moving, the view in not receiving any user interaction.
For example, if a have a UIButton that animates every time it's shown, but the user will not be able to click on it until the animation is over.
//This is a UIButton:
- (void)animationApear
{
CGRect frameSelf = self.frame;
frameSelf.origin.y -= frameSelf.size.height;
[UIView animateWithDuration:1.0 delay:0
usingSpringWithDamping:0.8 initialSpringVelocity:0
options:0
animations:^{
[self setFrame:frameSelf];
} completion:nil];
}
Is there any way to deal with this issue?
Thanks!
You need to supply the option UIViewAnimationOptionAllowUserInteraction.
Also, depending on your view architecture, if the button is within the animated view, or a subview thereof, then the actual location of the button isn't moving. Only the presentation layer of the button is moving, so that is why the button may not be receiving taps. A good test is to tap where the button was when the animation started (and make sure the option UIViewAnimationOptionAllowUserInteraction is on) to see if it is still receiving taps.
On solution, when you actually need to animate buttons, is to make repeated short transforms (CGAffineTransforms, for example) and have those movements in aggregate, create the visual effect of the animation. Though in this case the button itself will move, rather than simply it's presentation.
Is there any way to lock scrollview programatically at specific place with having native
apple bounce back effect?
I have infinite scrollview and would like to lock at some specific place. I found out that
I can use scrollViewWillEndDragging:withVelocity:targetContentOffset to figure out that the locking point
is going to be crossed and intervene like this:
[self setContentOffset:CGPointMake(LockPositionX, LockPositionY) animated:YES];
However this doesn't have native bounce back and rubber banding effect. I try to workaround it with
modifying scrollview.decelerationRate but it looks like it cannot have values other than
UIScrollViewDecelerationRateNormal or UIScrollViewDecelerationRateFast.
Maybe I'm missing something and there is some hidden way to achieve that?
add below code in scrollViewWillEndDragging:withVelocity:targetContentOffset
if(lockingpointreached)
{
[UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionCurveEaseIn animations:^ {
[self setContentOffset:CGPointMake(LockPositionX-20, LockPositionY-20)];
} completion:NULL];
}
Hope this will help you.
I found out that the easiest way is to actually use native bounce back implementation.
Event though our scrollView contentSize have e.g. vertical bounds from 0 to 1000 if we put some all the previous with negative origin (out of screen) its going to be visible (rendered) when we bounce back.
If then for some reason we want to move the locking point the previous elements we just have to shift all the elements down so that only elements above the locking points have negative origin
I am trying to flip a UIView around one edge of the view, as if the view were a page of a calendar with a rigid page moving over like so : Calendar.
I am trying to do it like so :
[UIView transitionWithView:self.upperCard
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromBottom
animations:^{
topView.frame = [self bottomHalfRectFromBounds];
bottomView.frame = [self topHalfRectFromBounds];
topView.flippedOver = YES;
bottomView.flippedOver = NO;
}
completion:NULL];
[self setNeedsDisplay];
The only problem with this method is that because the animation of the view's frame is Linear and so is the flipping animation the animation's are out of time with each other (This is due to the fact that flipping at a constant velocity the area which is visible of the view is proportional to Cos(t), learn some maths if you didn't know that ;P).
So basically I'm either looking for a way to make the frame animation have an easing function... or a completely alternative method... I don't want this to look like a page curl as it is going to be used for a scoreboard app, so please don't just tell me to use the UIViewAnimationOptionCurlDown option :P
Thanks for your time!
In general, animating your view’s frame is not a good way to approach this: it causes the view to have to redraw itself at every step of the animation, and, as you’ve noticed, doesn’t look like a flip so much as a linear scale.
Check out this answer—specifically the second part of it, discussing how to do it in CA—for a better approach.
I have a question regarding Xcode.
I found a tutorial on the net that allowed me to move an image around the screen.
Could you please explain how I can make my button move to the left and back to right with one click (I can't find this anywhere...), and immediately after that run another IBAction that allows me to switch to another subview? (I already have this code naturally...). I tried to add both IBActions in 1 centralized one, but it didn't seem to work :-( It opens in this case immediately the subview without showing me the animation.
What I tried:
The code obtained until now:
-(IBAction) aMove: (id) sender{
if (bMove == NO) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
btnTarget.transform = CGAffineTransformMakeTranslation(-30,0);
[UIView commitAnimations];
bMove = YES;
}else{
btnTarget.transform = CGAffineTransformIdentity;
bMove = NO;
}
}
-(IBAction) aAnimateActivate: (id) sender {
[self aMove:nil];
[self targetOpenView:nil]; //Opens the subview
}
I appreciate your help! Thanks!
concatenate works to combine animations and I just put two blocks of animations (two of the one shown below) after each other... :
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.3];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
CGAffineTransform transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1, 1),CGAffineTransformMakeTranslation(10, -50));
btnGuide.transform = transform;
[btnGuide setAlpha:0.0];
[UIView commitAnimations];
Basically implementing this code did the trick... Easy!
However... I needed some additional pieces of code for the further implementation such as:
[self performSelector:#selector(aGuide) withObject:self afterDelay:0.0];
(don't mind the selector name)
and:
[NSTimer scheduledTimerWithTimeInterval:.5 target:self selector:#selector(targetOpenView:) userInfo:nil repeats:NO];
Without looking at all of your program flow nor doing any actual testing (and I find when using Core Animation that the only way to be sure it works right is to code it and see if it does) the problem with the subview opening right away occurs because right after you invoke your "aMove" method to set up the first animation the thread continues with the next line of code, i.e., the [self] targetOpenView:nil statement, which immediately opens the subview and thus doesn't allow the first animation sequence to be shown. There is no pause to wait for the first animation to be completed. The animation, once commited, runs on its own thread while your code continues to run on the current thread (probably the application's main thread). That might not seem to be the most sensible way but you have to think of the code you write as the process to set up an animation that, once committed, is a separate entity that is free to run on its own (beside your code). The advantage to Apple's implementation is that you can set up a whole bunch of different animations which occur at the same time. One of Core Animation's design goals is to take away the need for the programmer to be handling all the starting and stopping of various animations and instead let the animation coordination be done using various methods of delay and duration or providing the means for one animations events to be observed (and acted upon) by other animations.
In order to do the animation(s) the way you want you will need use a method which only allows the second animation to begin once the first is over. One way (assuming that the subview change would be set up as an animation itself) is to use a completion: handler, an animation block that only begins upon completion of the first animation. Another way is to let the two animations "start together" but include a delay: parameter in the second animation that is equal to the length of the first animation. If the subview change is not done with an animation but is just done with code in the main thread then you need to set up a an animation delegate that is called when certain events occur in your animation, one of which is it's completion. Parameter(s) are passed to your delegate to tell you what is occurring and to which animation.
All of this is discussed, with examples, in the Animations section of the View Programming Guide to iOS (about 10 pages that will probably show you almost exactly how to do what you want):
http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/AnimatingViews/AnimatingViews.html#//apple_ref/doc/uid/TP40009503-CH6
Also, in order to set up the first animation to move the button somewhere and back again you might want to read the subtopic in that same section of the guide mentioned above: Implementing Animations That Reverse Themselves. I think it would be the cleanest way to do what you want.
(FYI, I'm better with the MacOS side of Core Animation than the iOS side but the "why did this happen immediately?" problem you have is a common pitfall when getting up to speed with how it works. Hope this explanation helps.)