Always animate UIView to user's tap location - iphone

I have a UIView that I want to animate to the position where the user's finger currently is.
So, when the touch begins, it has to animate to the tap location.
When the touch moves, it has to adjust its destination to the new location.
When the touch ends, it has to finish the animation to the last tap location.
I tried several things but none worked out. I tried creating a new animation with beginsFromCurrentState in touchesMoved, but that didn't work too.

It seems you're complicating this a little bit. If you are implementing touchesMoved, you are wanting a drag. If you are more interested in a tap, then touchesEnded is where you want to implement this. Either way, you just need to get the location in view and update your UIView's center based upon that location. If you wrap the change to the center value in a UIView animation block, it will animate to the position as it updates.
If you are using iOS 3.2 or later, you can use gesture recognizers which makes this pretty trivial:
// In viewDidLoad or some such
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(viewWasTapped:)];
[[self view] addGestureRecognizer:tapRecognizer];
Then declare your action selector:
- (void)viewWasTapped:(UITapGestureRecognizer*)recognizer
{
CGPoint location = [recognizer locationInView:[self view]];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0f]; // take a second to animate
[viewToMove setCenter:location];
[UIView commitAnimations];
}
This will animate the view from one location to another. This is referred to as implicit animation. Using Core Animation classes such CAKeyframeAnimation or CABasciAnimation would be explicit animation.

Need a few more details for this.. But are you sure you are using touchesBegan, touchesMoved and touchesEnded from the appropriate view (ie not the view you want to move)? Also make sure the userInteractionEnabled = YES. Try logging the touches from touchesBegan to make sure you are receiving the touches.
Or you might want to just check out Apples MoveMe sample code at https://developer.apple.com/library/ios/#samplecode/MoveMe/Introduction/Intro.html where they have solved all those problems for you.

Related

Detect "end decelerating" in a programmatically moved scrollview

I had a manually dragged scrollview, now I want a scrollview moved programmatically when I click a button, by this code:
offset = CGPointMake(scrollView.contentOffset.x+320, 0);
[UIScrollView beginAnimations:#"scrollAnimation" context:nil];
[UIScrollView setAnimationDuration:0.5];
[scrollView1 setContentOffset:offset];
[UIScrollView commitAnimations];
It works well but now the functions scrollViewBeginDragging and scrollViewEndDecelerating are not called. Begindragging is no problem for me, but I don't know how detect the end of de scrollview movement when it is fired programmatically.
Thanks in advance!
Implement the scrollViewDidEndScrollingAnimation: delegate method. That's exactly what it's for.
Well... you kind of know when that's supposed to get called, right? Those callbacks are intended for touches, so they won't work, but you're telling it to finish 0.5, so why not schedule a call to the delegate method yourself?
[self performSelector:#selector(scrollViewDidEndDecelerating:) withObject:scrollView afterDelay:0.5];

Problems with UIView animation

I am performing animation on my view. But I am facing few problems with that. I have to button's startAnimation and Stop Animation. For performing animation on my imageview I am using following code in startAnimation button's action:
UIViewAnimationOptions option = UIViewAnimationOptionRepeat + UIViewAnimationOptionAutoreverse;
[UIView animateWithDuration:2.5 delay:0.0 options:option
animations:^{
CGAffineTransform scale = CGAffineTransformMakeScale(2.0, 2.0);
CGAffineTransform translate = CGAffineTransformMakeTranslation(-50.0, -100.0);
CGAffineTransform mix = CGAffineTransformConcat(scale, translate);
imv.transform = mix;
}
completion:^(BOOL finished) {
}
];
This is working.
Now the problems I am facing are:
How to stop this animation on my stopAnimation button's action?
If ImageView is animation and I enter in background after that I again come in foreground ImageView sticks there and do not animates.
How to solve these issues?
On your stopAnimation button you would need to remove animations from the UIView's layer. First you must import Quartzcore.framework.
#import <QuartzCore/QuartzCore.h>
[yourView.layer removeAllAnimations];
As for your other issue of pausing your animation while going into the background and coming back to foreground, theres no way to 'pause' an animation. Your best bet would be to manually handle that situation by restarting your animation.
To stop the animation you can use
#import <QuartzCore/QuartzCore.h>
...
[imv.layer removeAllAnimations];
As for making an animation restart it is a complex problem and the answer depends on what you want the application to do when it restores. One possible answer would be to keep track of where you are animating the ImageView to and then in ApplicationDidEnterBackground you could cancel all the animations and simply move it there. Then when you restored it would be in the correct location.
If you actually want to continue the animations I would have a read of some other Stack Overflow answers and see if that helps.
Restoring animation where it left off when app resumes from background
iOS, Restarting animation when coming out of the background
For further reference, for the second part of your response you have to restore the matrix to the identity before doing anything:
imv.transform = CGAffineTransformIdentity;

iOS: Can I override pinch in/out behavior of UIScrollView?

I'm drawing a graph on a UIView, which is contained by a UIScrollView so that the user can scroll horizontally to look around the entire graph.
Now I want to zoom the graph when a user pinches in with two fingers, but instead of zooming in a view with the same rate for X and Y direction, I want to zoom only in the X direction by changing the X scale, without changing the Y scale.
I think I have to catch the pinch in/out gesture and redraw the graph, overriding the default zooming behavior.
But is there a way to do this?
I've been having a very difficult time to catch the pinch gesture on the UIScrollView, as it cancels the touches when it starts to scroll. I want the zooming to work even after the UIScrollView cancels the touches. :(
Thanks,
Kura
Although you cannot delete the existing pinch gesture recognizer, you can disable it and then add your own:
// Disable existing recognizer
for (UIGestureRecognizer* recognizer in [_scrollView gestureRecognizers]) {
if ([recognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
[recognizer setEnabled:NO];
}
}
// Add our own
UIPinchGestureRecognizer* pinchRecognizer =
[[UIPinchGestureRecognizer alloc] initWithTarget:self
action:#selector(pinch:)];
[_scrollView addGestureRecognizer:pinchRecognizer];
[pinchRecognizer release];
Then in
- (void) pinch:(UIPinchGestureRecognizer*)recognizer { .. }
use
[recognizer locationOfTouch:0 inView:..]
[recognizer locationOfTouch:1 inView:..]
to figure out if the user is pinching horizontally or vertically.
You should instead access the gestureRecognizers (defined in UIView), there are several of them being used by the scroll view,
figure out which one is the pinch recognizer and call removeGestureRecognizer: on the scroll view, then create your own and have it do the work, add it back with addGestureRecognizer:.
these are all public API,
the recognizers and what order they are in are not (currently),
so program defensively when accessing them
(this is a perfectly valid way to manipulate UIKit views, and Apple won't/shouldn't have issues with it - though they will not guarantee it works in any future release)
You should be able to subclass UIScrollView and override the touchesBegan: method. Don't call [super touchesBegan:] but instead, adjust the zoom as you like:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//Anything you want. Probably you would want to store all the touches
//or their values, so that you can compare them to the touches
//in the touchesEnded: method,
//thus letting you know what the pinch amount was
}
If you like, you can judge whether it's a pinch or not, and if it's not, call the super method, and only handle it yourself for custom pinches.
Edsko & bshirley answers are good, but they don't tell where to place the code.
First, I placed it in viewDidLoad method, but no Pinch Gesture Recognizer was found in the scrollview (maybe because my scrollview is an IBOutlet).
Then I tried in viewWillAppear or viewDidAppear and the UIPinchGestureRecognizer was here.

Is it possible to change UIView's "center" property inside the overriden touchesEnded method and redraw the view?

I want to implement a simple animation based on UIView's center property. I have a simple view and I can drag it (UIView touchesMoved is overriden). The animation should fade slowly like the view is moved under it's own inertia for some time after the user releases it. But for now I want simply to move the view after touch ends. Here is the code I have in touchesEnded:
int i;
for (i=1;i<4;i++)
{
self.center = CGPointMake(10*i, 12*i);
[self setNeedsDisplay];
usleep(100000);
}
The problem is when I run this, the code is executed nicely but "UIView" changes to late. I changed usleep time and other parameters but the result is the same. It looks like all the "pending changes" in the view are performed only after the overriden touchesEnded is finished.
Is this the right way of implementing such user interface feature or should I look for some other approaches?
Thanks in advance.
If you know the final positions you want your view center to have you could do this with an animation:
[UIView animateWithDuration:20
delay:0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
youView.center = CGPointMake(newCenterX, newCenterY);
}
completion:^(BOOL finished){
}];
If that code is being run inside of touchesEnded you are blocking the main thread and that is why the animation happens all at once when its done. You will need run this loop in the background to update the view over time. When you update the UI remember to always call back to the main thread.
Some Threading Options:
performSelectorInBackground:withObject:
performSelectorOnMainThread:withObject:waitUntilDone:
Grand Central Dispatch
Threading

hot to disable touch handling in CCLayer in cocos2d

I've got a CCLayer subclass i'm using to display some sprites and to show some animations. Also it has a CCMenu with some items. When user selects some of the menu item i want to run an animation and then to show another scene. But i want user not to be able to touch anything on the screen while animation is running.
Of course, i can just disable handling touches in my callbacks, but maybe there is more simple way - just to disable all touch handling for a while ?
Disable touch dispatcher before animation running and enable touch dispatcher after animation stopped. Here is the code snippet:
[[CCDirector sharedDirector] touchDispatcher].dispatchEvents = NO;
CCAnimation* animation = [CCAnimation animationWithFrame:#"numberexplode" frameCount:5 delay:0.2];
CCAnimate* animate = [CCAnimate actionWithAnimation:animation];
CCCallBlock* completion = [CCCallBlock actionWithBlock:^{
[[CCDirector sharedDirector] touchDispatcher].dispatchEvents = YES;
}];
CCSequence* sequence = [CCSequence actions:animate, completion, nil];
[self runAction:sequence];
You want to look at the CCTouchDispatcher singleton class. If you add a targeted touch handler that swallows touches (and does nothing) then you won't get any touches handled. As far as I can tell there's no way to totally disable touches.
Alternatively you can make a new CCLayer that's on top of everything else (I think z order really high will do this), and make it clear, and have it do nothing with touches.
hope that helps.