How to attach a click event to an animated button in ios - ios5

I am having a moveable button and would like to execute a method when the user click on that button - is that possible in ios?
I have write the following code but the animation needs to be finished in order to be able to trigger that function:
[arrowButton addTarget:self action:#selector(presentMainWindow) forControlEvents:UIControlEventTouchUpInside];
[UIView animateWithDuration:kAnimationDuration
delay:.2f
options:UIViewAnimationOptionCurveEaseInOut
animations:
^{
[arrowButton setTransform:CGAffineTransformMakeTranslation(kButtonShift, 0)];
[arrowButton setFrame:CGRectMake(arrowButton.frame.origin.x + kButtonShift, arrowButton.frame.origin.y, arrowButton.frame.size.width, arrowButton.frame.size.height);
}
completion:nil];

I found out that you can simply add the option UIViewAnimationOptionAllowUserInteraction to the animation to handle both the animation and the interaction:
[UIView animateWithDuration:kAnimationDuration
delay:.2f
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction
animations:
^{
[arrowButton setTransform:CGAffineTransformMakeTranslation(kButtonShift, 0)];
}
completion:nil];

as long as the animation is only an effect what you can see
and not can touch you can only click the button while animating if you make your own animation like this
i dont know if this is the best solution but it is an working solution.
- (IBAction)startanimation:(id)sender {
old = CGPointMake(arrowButton.frame.origin.x,arrowButton.frame.origin.x);
[self ani];
}
- (void)ani {
NSLog(#"ani1");
if (arrowButton.frame.origin.x < old.x + kButtonShift ) {
NSTimer*myTimer;
myTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeInterval/kButtonShift*4 target:self selector:#selector(ani2) userInfo:nil repeats:NO];
}
}
- (void)ani2 {
NSLog(#"ani2");
arrowButton.frame = CGRectMake((arrowButton.frame.origin.x + 1), arrowButton.frame.origin.y, arrowButton.frame.size.width, arrowButton.frame.size.height);
[self ani];
}

Related

Interrupt completion block based animations to perform separate animations

I am using block based animations to simulate dealing cards as an intro animation for a game. The animation works great unless the user causes a segue to fire DURING the animation, where we perform additional animations in order to get the effect a "sliding" transition from source to destination view controller. What happens now is the cards that have already been "dealt" slide off screen appropriately, and if there are cards that have not been dealt, it deals it in the middle of the transition and then the card disappears when the destination view controller is pushed. It's very ugly.
I have tried 'view.layer removeAllAnimations' which didn't help (I did import quartzcore). What I want to do is cancel the pending animations in the completion blocks, and simply perform the segue animations.
Here's the "dealing" code:
[UIView animateWithDuration:0.20f
delay:0.20f
options:UIViewAnimationOptionCurveEaseOut
animations:^
{
_fiveOfHearts.center = CGPointMake(90, 198);
_fiveOfHearts.transform = fiveOfHeartsTransform;
}
completion:^(BOOL finished)
{[UIView transitionWithView:_fiveOfHearts duration:0.20f options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
_fiveOfHearts.image = [UIImage imageNamed:#"52"];
}completion:nil];
[UIView animateWithDuration:0.30f
delay:0.0f
options:UIViewAnimationOptionCurveEaseOut
animations:^
{
_jackOfHearts.center = CGPointMake(128, 196);
_jackOfHearts.transform = jackfHeartsTransform;
}
completion:^(BOOL finished)
{[UIView transitionWithView:_jackOfHearts duration:0.40f options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
_jackOfHearts.image = [UIImage imageNamed:#"112"];
}completion:nil];
[UIView animateWithDuration:0.30f
delay:0.0f
options:UIViewAnimationOptionCurveEaseOut
animations:^
{
_aceOfHearts.center = CGPointMake(162, 196);
_aceOfHearts.transform = aceOfHeartsTransform;
}
completion: ... and so on.
The segue code looks something like:
for (UIView *iv in src.view.subviews) {
if (iv.tag != 99999) {
[UIView animateWithDuration:0.5f animations:^{iv.center = CGPointMake(iv.center.x - 600, iv.center.y);}];
}
}
You could add a global BOOL say hasUserSkippedOn once the user presses to move on set it to YES and then every time you hit a completion block check if _hasUserSkipped is still YES then do not proform any more. Normally on blocks it has a default end bool but I am not too sure if animations blocks have the end bool.

When a UIButton is animating I want to recognize a event on the button [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
UIButton can’t be touched while animated with UIView animateWithDuration
I want to animate a UIButton from left to right, while its animating if user touch the button I should be sent an event, but when the button animating it isn't send event. Please help me, my project is stoped on this point.
Some developer suggested me to use
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
myBtn.frame=CGRectMake(0,
100,
myBtn.frame.size.width,
myBtn.frame.size.height);
}
completion:^(BOOL finished) { NSLog(#"Animation Completed!"); }];
this method but it is not working too, please tell what should I do???
You should use tapGesture recogniser for getting the Tap event to that button
as below in viewDidLoad.
- (void)viewDidLoad
{
UITapGestureRecognizer *btnTapped = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapAction:)];
btnTapped.numberOfTapsRequired = 1;
btnTapped.delegate = self;
[myBtn addGestureRecognizer:btnTapped];//here your button on which you want to add sopme gesture event.
[btnTapped release];
[super viewDidLoad];
}
And That's Your Code for animating the Button use as it is .
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
myBtn.frame=CGRectMake(0, 100, myBtn.frame.size.width, myBtn.frame.size.height);
}
completion:^(BOOL finished) {NSLog(#"Animation Completed!");];
Below is the Delegate method for allowing simultaneous recognition
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
here Above methods Returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
- (void)tapAction:(UITapGestureRecognizer*)gesture
{
//do here which you want on tapping the Button..
}
EDIT: If You want to find the touch gesture you should use UILongPressGestureRecognizer instead of UITapGestureRecognizer and set the duration.
I hope it may help you .
The UIButton cant be used while it's under animation, you have to use an NSTimer:
timer = [NSTimer timerWithTimeInterval:.005
target:self
selector:#selector(moveButton)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] timer forMode:NSRunLoopCommonModes];
// you can change the speed of the button movement by editing (timerWithTimeInterval:.005) value
-(void)moveButton{
button.center = CGPointMake(button.center.x+1,button.center.y);
if (button.frame.origin.x>=self.view.frame.size.width ) {
[timer invalidate];
//The event that will stop the button animation
}
}
The problem is that the button already gets the final frame and doesn't work with the current position.
#jrturton has given a good solution in this question: UIButton can't be touched while animated with UIView animateWithDuration
It basically implements the touchesBegan: method to work with the presentationLayer.

One call function for UIView animation

In my project, I've got 10 of these code here:
- (void) makeAnim1{
//downward animation for carousel
[UIView animateWithDuration:**3.5**
delay:0.25
options: UIViewAnimationCurveLinear
animations:^{
carousel.frame = CGRectOffset(carousel.frame, 0, **490**);
}
completion:^(BOOL finished){ //task after an animation ends
[self performSelector:#selector(makeAnim1_1) withObject:nil afterDelay:3.0];
NSLog(#"Done!");
}];
}
- (void) makeAnim1_1{
//upward animation for carousel
[UIView animateWithDuration:**3.5**
delay:0.2
options: UIViewAnimationCurveLinear
animations:^{
carousel.frame = CGRectOffset(carousel.frame, 0, **-480**);
}
completion:^(BOOL finished){
//timer for the reveal button
[NSTimer scheduledTimerWithTimeInterval:0.1 //this arranges the duration of the scroll
target:self
selector:#selector(revealButton)
userInfo:nil
repeats:NO];
NSLog(#"Done!");
}];
}
I wanted to make them in just one short implementation. So they will be just one function.
I've tried putting the needed values and assigning them in the bold version of the code.
I need to have two arrays that corresponds to each other. For example:
arrayA[3] = A,B,C;
arrayB[3] = a,b,c;
When A is pick it is equal to a.
Try this below code :
You can convert Array Values to Uppercase or lowercase like below code:
A array contains first index is A to convert a
B array contains first index is a
if([A objectAtIndex:0] lowercaseString].isEqualString([B objectatindex:0])
{
}
Thanks...!

How to have a handler to repeat UIView animateWithDuration?

I'm using UIView class method animateWithDuration for repeating my view animation. How can I have a handler that could be used to stop this animation later? For example, repeated animation starts in one method and I need to stop it later from another method.
You could do something like this assuming you have created a canceled property. As noted in the comments the completion block's startAnimation call needs to be wrapped in an async call to avoid a stack overflow. Be sure to replace the "id" with whatever class type you actually have.
- (void)startAnimation {
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^(void) {
//animate
}
completion:^(BOOL finished) {
if(!self.canceled) {
__weak id weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf startAnimation];
}];
}
}
];
}
The purpose of the animation is to repeatedly animate the bounce of an image. When there is no worry about manually stopping it then you just need to set three properties (UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat) and animation block code for moving the image - self.image.center = CGPointMake(self.image.center.x, self.image.center.y+25); Here is the full code of the animation:
[UIView animateWithDuration:0.5 delay:0 options:( UIViewAnimationOptionCurveEaseIn |
UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat |
UIViewAnimationOptionAllowUserInteraction) animations:^{self.image.center =
CGPointMake(self.image.center.x, self.image.center.y+25);} completion:nil];
That's it. But if you need a manual control then some additional code is required. First, according to jaminguy, you need to have a BOOL property for indication loop/stop (self.playAnimationForImage) the animation and clean separate method with animation code that would be called from elsewhere. Here is the method:
-(void)animateImageBounce{
[UIView animateWithDuration:0.5 delay:0 options:(
UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionAllowUserInteraction) animations:^{self.image.center =
CGPointMake(self.image.center.x, self.image.center.y+25);} completion:^(BOOL finished){if
(finished && self.playAnimationForImage){
self.image.center = CGPointMake(self.image.center.x, self.image.center.y-25);
[self animateImageBounce];
}}];
and here is the start of the animation call from some method
-(void)someMethod{
...
self.playAnimationForFingers = YES;
[self animateImageBounce];
}
The thing that I would like to note is that, in manual control, you need to reset the center.y of the image back right before next recursive call is performed.
Actually, the solution with recursive call didn't worked out for me. The animation started to behave weirdly: every 2- 3 animatation repeat cycle I got animation breaks. After the first bouncing part of item (moving item down) the second part (moving up) was performing almost instantly. I thing it has something to do with the recursive call.
Therefore, I refused to use that. The solution would be to start the animation with autoreverse and repeat options and, in complete block, to check if a flag (self.playAnimationForFingers) indicates to stop the animation.
-(void)animateFingersForLevelCompleteScreen{
//fix: reset the place of bouncing fingers (starting place).
//Otherwise the fingers will slowly move to the bottom at every level.
//This resetting is not working when placed inside UIView
//animate complete event block
self.image.center = CGPointMake(10 + self.image.frame.size.width/2,
95 + self.image.frame.size.height/2);
[UIView animateWithDuration:0.5 delay:0
options:(UIViewAnimationOptionCurveEaseIn |
UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionRepeat |
UIViewAnimationOptionAllowUserInteraction)
animations:^{
self.image.center = CGPointMake(self.image.center.x,
self.image.center.y+25);
}
completion:^(BOOL finished){
/*finished not approapriate: finished will not be TRUE if animation
was interrupted. It is very likely to happen because animation repeats && */
if (!self.playAnimationForFingers){
[UIView setAnimationRepeatAutoreverses:NO];
}
}];
}
U can make use of CABasicAnimation instead.
CABasicAnimation *appDeleteShakeAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
appDeleteShakeAnimation.autoreverses = YES;
appDeleteShakeAnimation.repeatDuration = HUGE_VALF;
appDeleteShakeAnimation.duration = 0.2;
appDeleteShakeAnimation.fromValue = [NSNumber numberWithFloat:-degreeToRadian(5)];
appDeleteShakeAnimation.toValue=[NSNumber numberWithFloat:degreeToRadian(5)];
[self.layer addAnimation:appDeleteShakeAnimation forKey:#"appDeleteShakeAnimation"];
Then when u want to stop it you can just call
[self.layer removeAnimationForKey:#"appDeleteShakeAnimation"];

Change the speed of setContentOffset:animated:?

Is there a way to change the speed of the animation when scrolling a UITableView using setContentOffset:animated:? I want to scroll it to the top, but slowly. When I try the following, it causes the bottom few cells to disappear before the animation starts (specifically, the ones that won't be visible when the scroll is done):
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:3.0];
[self.tableView setContentOffset:CGPointMake(0, 0)];
[UIView commitAnimations];
Any other way around this problem? There is a private method _setContentOffsetAnimationDuration that works, but I don't want to be rejected from the app store.
[UIView animateWithDuration:2.0 animations:^{
scrollView.contentOffset = CGPointMake(x, y);
}];
It works.
Setting the content offset directly did not work for me. However, wrapping setContentOffset(offset, animated: false) inside an animation block did the trick.
UIView.animate(withDuration: 0.5, animations: {
self.tableView.setContentOffset(
CGPoint(x: 0, y: yOffset), animated: false)
})
I've taken nacho4d's answer and implemented the code, so I thought it would be helpful for other people coming to this question to see working code:
I added member variables to my class:
CGPoint startOffset;
CGPoint destinationOffset;
NSDate *startTime;
NSTimer *timer;
and properties:
#property (nonatomic, retain) NSDate *startTime;
#property (nonatomic, retain) NSTimer *timer;
and a timer callback:
- (void) animateScroll:(NSTimer *)timerParam
{
const NSTimeInterval duration = 0.2;
NSTimeInterval timeRunning = -[startTime timeIntervalSinceNow];
if (timeRunning >= duration)
{
[self setContentOffset:destinationOffset animated:NO];
[timer invalidate];
timer = nil;
return;
}
CGPoint offset = [self contentOffset];
offset.x = startOffset.x +
(destinationOffset.x - startOffset.x) * timeRunning / duration;
[self setContentOffset:offset animated:NO];
}
then:
- (void) doAnimatedScrollTo:(CGPoint)offset
{
self.startTime = [NSDate date];
startOffset = self.contentOffset;
destinationOffset = offset;
if (!timer)
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(animateScroll:)
userInfo:nil
repeats:YES];
}
}
you'd also need timer cleanup in the dealloc method. Since the timer will retain a reference to the target (self) and self has a reference to the timer, some cleanup code to cancel/destroy the timer in viewWillDisappear is likely to be a good idea too.
Any comments on the above or suggestions for improvement would be most welcome, but it is working very well with me, and solves other issues I was having with 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 NSTimerswith
-[UIScrollView setContentOffset:(CGPoint*)];`
Hope it helps
UIView calculates final view and then animates it. That's why cells that invisible on finish of animation invisible on start too. For prevent this needed add layoutIfNeeded in animation block:
[UIView animateWithDuration:2.0 animations:^{
[self.tableView setContentOffset:CGPointMake(0, 0)];
[self.tableView layoutIfNeeded]
}];
Swift version:
UIView.animate(withDuration: 2) {
self.tableView.contentOffset.y = 10
self.tableView.layoutIfNeeded()
}
I'm curious as to whether you found a solution to your problem. My first idea was to use an animateWithDuration:animations: call with a block setting the contentOffset:
[UIView animateWithDuration:2.0 animations:^{
scrollView.contentOffset = CGPointMake(x, y);
}];
Side effects
Although this works for simple examples, it also has very unwanted side effects. Contrary to the setContentOffset:animated: everything you do in delegate methods also gets animated, like the scrollViewDidScroll: delegate method.
I'm scrolling through a tiled scrollview with reusable tiles. This gets checked in the scrollViewDidScroll:. When they do get reused, they get a new position in the scroll view, but that gets animated, so there are tiles animating all the way through the view. Looks cool, yet utterly useless. Another unwanted side effect is that possible hit testing of the tiles and my scroll view's bounds is instantly rendered useless because the contentOffset is already at a new position as soon as the animation block executes. This makes stuff appear and disappear while they're still visible, as to where they used to be toggled just outside of the scroll view's bounds.
With setContentOffset:animated: this is all not the case. Looks like UIScrollView is not using the same technique internally.
Is there anyone with another suggestion for changing the speed/duration of the UIScrollView setContentOffset:animated: execution?
You can set the duration as follows:
scrollView.setValue(5.0, forKeyPath: "contentOffsetAnimationDuration") scrollView.setContentOffset(CGPoint(x: 100, y: 0), animated: true)
This will also allow you to get all of your regular delegate callbacks.
https://github.com/dominikhofmann/PRTween
subclass UITableview
#import "PRTween.h"
#interface JPTableView : UITableView{
PRTweenOperation *activeTweenOperation;
}
- (void) doAnimatedScrollTo:(CGPoint)destinationOffset
{
CGPoint offset = [self contentOffset];
activeTweenOperation = [PRTweenCGPointLerp lerp:self property:#"contentOffset" from:offset to:destinationOffset duration:1.5];
}
IF all your trying to do is scroll your scrollview I think you should use scroll rect to visible. I just tried out this code
[UIView animateWithDuration:.7
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGRect scrollToFrame = CGRectMake(0, slide.frame.origin.y, slide.frame.size.width, slide.frame.size.height + kPaddingFromTop*2);
CGRect visibleFrame = CGRectMake(0, scrollView.contentOffset.y,
scrollView.frame.size.width, scrollView.frame.size.height);
if(!CGRectContainsRect(visibleFrame, slide.frame))
[self.scrollView scrollRectToVisible:scrollToFrame animated:FALSE];}];
and it scrolls the scrollview to the location i need for whatever duration i am setting it for. The key is setting animate to false. When it was set to true, the animation speed was the default value set by the method
For people who also have issues with disappearing items while scrolling a UITableView or a UICollectionView you can expand the view itself so that we hold more visible items. This solution is not recommended for situations where you need to scroll a great distance or in situations where the user can cancel the animation. In the app I'm currently working on I only needed to let the view scroll a fixed 100px.
NSInteger scrollTo = 100;
CGRect frame = self.collectionView.frame;
frame.size.height += scrollTo;
[self.collectionView setFrame:frame];
[UIView animateWithDuration:0.8 delay:0.0 options:(UIViewAnimationOptionCurveEaseIn) animations:^{
[self.collectionView setContentOffset:CGPointMake(0, scrollTo)];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.8 delay:0.0 options:(UIViewAnimationOptionCurveEaseIn) animations:^{
[self.collectionView setContentOffset:CGPointMake(0, 0)];
} completion:^(BOOL finished) {
CGRect frame = self.collectionView.frame;
frame.size.height -= scrollTo;
[self.collectionView setFrame:frame];
}];
}];
I use transitionWithView:duration:options:animations:completion:
[UIView transitionWithView:scrollView duration:3 options:(UIViewAnimationOptionCurveLinear) animations:^{
transitionWithView:scrollView.contentOffset = CGPointMake(contentOffsetWidth, 0);
} completion:nil];
UIViewAnimationOptionCurveLinear is an option make animation to occur evenly over.
While I found that in an animation duration, the delegate method scrollViewDidScroll did not called until animation finished.
You can simply use block based animation to animate the speed of scrollview.
First calculate the offset point to which you want to scroll and then simply pass that offset value as here.....
[UIView animateWithDuration:1.2
delay:0.02
options:UIViewAnimationCurveLinear
animations:^{
[colorPaletteScrollView setContentOffset: offset ];
}
completion:^(BOOL finished)
{ NSLog(#"animate");
} ];
here colorPaletteScrollView is my custom scrollview and offset is the value passed .
this code works perfectly fine for me.
Is there a reason you're using setContentOffset and not scrollRectToVisible:animated:?
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
I would recommend doing it like this:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:3.0];
[self.tableView scrollRectToVisible:CGRectMake(0, 0, 320, 0) animated:NO];
[UIView commitAnimations];
Unless that doesnt work. I still think you should try it.
Actually TK189's answer is partially correct.
To achieve a custom duration animated contentOffset change, with proper cell reuse by UITableView and UICollectionView components, you just have to add a layoutIfNeeded call inside the animations block:
[UIView animateWithDuration:2.0 animations:^{
tableView.contentOffset = CGPointMake(x, y);
[tableView layoutIfNeeded];
}];
On Xcode 7.1 - Swift 2.0 :
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(0, animations: { self.scrollView!.setContentOffset(CGPointZero,animated: true) })
}
return true
}
OR
func textFieldShouldReturn(textField: UITextField) -> Bool {
if(textField.returnKeyType == UIReturnKeyType.Next) {
password.becomeFirstResponder()
}
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(0, animations: { self.scrollView!.setContentOffset(CGPointZero,animated: true) })
}
textField.resignFirstResponder()
return true
}
Note: self.scrollView!.setContentOffset(CGPointZero,animated: true) can have different positions depending on the requirement
Example:
let scrollPoint:CGPoint = CGPointMake(0,textField.frame.origin.y/2);
scrollView!.setContentOffset(scrollPoint, animated: true);
I wanted to change the contentOffSet of tableview when textfield begins to edit.
Swift 3.0
func textFieldDidBeginEditing(_ textField: UITextField) {
DispatchQueue.main.async {
UIView.animate(withDuration: 0, animations: {
self.sampleTableView.contentOffset = CGPoint(x: 0, y: 0 - (self.sampleTableView.contentInset.top - 200 ))
})
}
}