I'm animating a couple of views with animateWithDuration: and i'm simply unable to detect any touches on them.
I've tried simple touch handling (touchesEnded:) and a tapGestureRecognizer.
First i've animated them with CGAffineTransformTranslation but then i realized that this won't if i check the coordinate with touchesMoved: so i switched to animate the frame property. I quickly noticed, that the frame values are not really changing during the animation so i dropped the touchesEnded: idea. So i changed to a tapGestureRecognizer which doesn't work too.
I've enabled userInteraction on all views and i also added the option UIViewAnimationOptionAllowUserInteraction to the animation.
Here's the code of the animation and the stuff that happens before:
// init the main view
singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapOnItem:)]; // singleFingerTap is an ivar
// code somewhere:
// userItem is a subview of MainView
[userItem addGestureRecognizer:singleFingerTap];
[UIView animateWithDuration:animationTime
delay:0.0f
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^{
userItem.frame = CGRectMake(-(self.bounds.size.width + userItem.frame.size.width), userItem.frame.origin.y, userItem.frame.size.width, userItem.frame.size.height);
}
completion:^(BOOL finished) {
if (finished) {
[unusedViews addObject:userItem];
[userItem removeFromSuperview];
[userItem removeGestureRecognizer: singleFingerTap];
}
}
];
And here's the gesture recognizer:
- (void) handleTapOnItem:(UITapGestureRecognizer *)recognizer{
NSLog(#"Touched");
}
So how can i actually get a touch from an animated View or what is the best solution?
Adding transparent uibutton to each view is not an option. :(
When an animation is applied to a view, the animated property changes to its end value right away. what you are actually seeing on screen is the presentation layer of your views layer.
I wrote a blog post about hit testing animating views/layers a while back that explain it all in more detail.
While anything is being moved using Core Animation touches on the object are halted until the object completes its movement. Interesting tid-bit, if you move the object and touch where its final location will be, the touch is picked up. This is because as soon as the animation is started the frame of the object is set to the ending point, it is not updated as it moves across the screen.
You might have to look into alternate technologies like Quartz or OpenGL ES to detect touches on your views as they are being moved.
Related
I am intercepting all events (for debugging purposes) in my custom UIApplication subclass:
[super sendEvent:event];
if(event.type == UIEventTypeTouches){
if(event.allTouches.anyObject.phase == UITouchPhaseBegan){
// NSLog(#"Touch began on view %#", event.allTouches.anyObject.view);
}
}
It is working for any touch, regardless of the view, gesture recognizers, user interaction etc.
However, I've got a MKMapView that isn't receiving any touches (user interaction is enabled) and not even forwarding touches to the application instance. In other words, when I touch that specific MKMapView, sendEvent: isn't called on application.
How is this even possible? As far as I know, no matter what I do, UIApplication should always receive a touch at the sendEvent: method.
Am I missing something? (I am on iOS 11.4)
I've finally solved the problem.
I was constantly animating the map according to the user's orientation (compass, tilt etc.) to create a 3D effect even if the device was tilted/moved a single bit, which seems to happen continuously:
...
motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.033;
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:frame
toQueue:motionQueue
withHandler:
^(CMDeviceMotion* motion, NSError* error){
//update map
}];
...
In the map update method, I was animating the map to the new camera data to smooth out the update visually:
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveLinear
animations:block completion:nil];
Because of the constant animation, the view was ignoring touch input.
I've added UIViewAnimationOptionAllowUserInteraction to my animation options, which solved the issue:
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveLinear
| UIViewAnimationOptionAllowUserInteraction animations:block completion:nil];
I have a subclass of UIScrollView that I'm using for images slideshow, with infinite scrolling and circular slideshow.
I used to animate the transition in this way: (Because I wanted the transition to be slower)
[UIView animateWithDuration:1.0
delay:0 options:(UIViewAnimationCurveEaseOut)
animations:^{
self.scrollView.contentOffset = newOffset;}
completion:NULL];
And it worked just fine.
Then I watched the lecture "Advanced Scrolling Techniques" from WWDC 2011, and they recommend to implement infinite scrolling by overriding layoutSubviews.
So I changed my implementation and override layoutSubviews
Once I did that the transition animation stopped working.
If I comment out my custom layoutSubviews - It's working again!
Why??
What can I do to make my own scrolling animation while overriding layoutSubviews?
Thanks!
OK, I think I found the problem, and a possible fix.
The key to understand the problem is understanding that when you call
[UIView animateWithDuration...]
All the properties that you change inside the animation block, are changed immediately. They do not change as the animation actually executing (visually).
So in my case, I'm animating the contentOffset to my target value.
This operation invokes layoutSubviews immediately.
In my implementation of layoutSubviews I'm checking to see if enough movement has been accumulated. If so - I'm rearranging my ImageViews, and set the contentOffset to its initial value. Once I do that there is nothing to animate anymore, because nothing has changed!
Animate contentOffset to X:
calls setContentOffset to X:
invokes layoutSubview
Sets the contentOffset back to Y! (after rearranging the views)
Nothing to animate at this point...
Possible Fix
I'm not sure if it is the right way to do it, but its working working.
So what we need to do is to ensure that no changes will be made in layoutSubviews while we are in the middle of animation block.
So I created a BOOL instance variable named isAnimating, and set it appropriately in the animation block:
self.isAnimating = YES;
[UIView animateWithDuration:self.transitionDuration
delay:0
options:(UIViewAnimationOptionCurveEaseOut)
animations:^{
self.contentOffset = newOffset;
}
completion:^(BOOL finished) {
self.isAnimating = NO;
[self setNeedsLayout];
}];
Now in layoutSubviews we need to take isAnimating into account:
-(void) layoutSubviews
{
[super layoutSubviews];
if (self.isAnimating == NO)
{
[self rearrangeIfNecessary];
}
}
And it's working again!
I am using a UIView animation to resize and translate a view containing multiple subviews. The animation for the parent view happens perfectly; however, the subviews exhibit strange behaviour. When the animation begins, the subviews are immediately resized and then moved to their final position.
For example, if the duration and delay of the animation was five-seconds, as soon as the animation was called, the subviews would move to the desired end-of-animation values. After five-seconds, the superview would be resized and translated to the desired.
My code for the animation is:
[UIView animateWithDuration:0.5 animations:^{
if (UIDeviceOrientationIsLandscape(self.interfaceOrientation)) {
self.leftPaneView.frame = leftPaneLandscapeFrame;
self.rightPaneContainerView.frame = rightPaneLandscapeFrame;
}
if (UIDeviceOrientationIsPortrait(self.interfaceOrientation)) {
CGFloat offset = 300;
self.leftPaneView.frame = CGRectOffset(leftPanePortraitFrame, -offset, 0);
self.rightPaneContainerView.frame = rightPanePortraitFrame;
}
}];
Any ideas?
Note: rightPaneContainerView contains the view of a UIViewController that is a child of the view controller that calls this animation.
I managed to solve the problem. The content mode for some of the views was set to Left. When the animation started, the views would jump the left, and then be animated to the desired end-of-animation value.
An amateur mistake. Thanks everyone who took a look.
I'm trying to recreate the flipping album animation in iPod.app on the iPad (Music.app in iOS 5). Getting the flipping to work is easy, but I'm having trouble with positioning and enlarging the album. Right now I'm using this code:
[UIView transitionWithView:self.containerView duration:5.0 options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionShowHideTransitionViews animations:^(void) {
self.firstView.hidden = YES;
self.secondView.hidden = NO;
self.containerView.frame = CGRectMake(600.0, 0.0, 168.0, 1004.0);
} completion:nil];
The flipping works, but there is something strange going on in the animation. The container view does indeed move and resize, but the subviews (firstView and secondView) do not.
Because the superview clips to its bounds (even though I set that to NO; another strange thing!), it appears like the subviews are getting "cut" when the container view moves.
I hope you guys understand the problem. Any Core Animation hero who can help me with this? Thanks.
Have you set the autoresize mask on the child views? Those are used to automatically resize or reposition a view when its superview’s bounds change.
Alright this is a newbie question, so I apologize in advance. I have a UIView which I laid out in Interface Builder off-screen. When the user presses a button, I would like to animate this view on-screen. It works, but I can't interact with any buttons in the UIView. I have read that only the view is moved when you animate and that the actual objects retain their positions, so I have tried setting the position of the UIViews' layers, but it causes my UIView menu to display an extra 81 pixels to the left. When this happens, I can interact with the buttons, but I need it to be flush with the right of the screen. Here is my code in the IBAction on the openMenu button:
CABasicAnimation *moveView = [CABasicAnimation animationWithKeyPath:#"position"];
moveView.delegate = self;
moveView.duration=0.5;
// If the menu is displayed, close it!
CGPoint currentPosView = [[entirePage layer] position];
CGPoint destView;
[moveView setFromValue:[NSValue valueWithCGPoint:currentPosView]];
if (menuDisplayed) {
// Move right to our destination
destView.x = currentPosView.x;
destView.y = currentPosView.y + 81;
[moveView setToValue:[NSValue valueWithCGPoint:destView]];
// Otherwise, open it
} else {
// Move left to our destination
destView.x = currentPosView.x;
destView.y = currentPosView.y - 81;
[moveView setToValue:[NSValue valueWithCGPoint:destView]];
}
// Animate the view
[[entirePage layer] addAnimation:moveView forKey:#"move"];
// Set the final position of our layer
[[entirePage layer] setPosition:destView];
menuDisplayed = !menuDisplayed;
And then in the animationDidStop method:
CGPoint currentPosMenu = [[menuBar layer] position];
if (menuDisplayed) {
currentPosMenu.x -= 81;
} else {
currentPosMenu.x += 81;
}
[[menuBar layer] setPosition:currentPosMenu];
Help???
You shouldn't need to mess with the layers. You could probably achieve the effect you want by laying out the views in Interface Builder at the positions they will be in after they are animated into view. In other words, first lay things out in their final/correct positions. Don't do anything funky in IB.
In your code, after the app is launched or in a -viewDidAppear: method if you're using a UIViewController, you could offset the views by some amount and then restore them to their original position in an animation block:
myView.transform = CGAffineTransformMakeTranslation(0,81);
[UIView beginAnimations:#"slideUp" context:nil];
myView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
As for the lack of user interaction on your views, that could be a number of things. If all of your views are subviews of something like a UIImageView, UIImageView has userInteractionEnabled set to NO by default (at least if you build one in code - I'm not sure off hand what IB's default settings are for an image view). It could also mean that the superview of your views is actually too small and its frame does not contain the subviews. (If clipping was turned on for the superview, you wouldn't even be able to see subviews that are having this particular problem.)
EDIT:
I read things more carefully and see you want things to move in response to a button press, so in other words the views will need to be hidden from the start. In that case you could still lay everything out in IB in their final position, but just set them as hidden in IB. When you push the button, set the views visible (hidden=NO) before translating them. That should allow them to be laid out in their final/correct place in IB making sure that they are positioned on the proper superviews, etc while still getting the animation effect you want.
Forget about the animations for now; you should move your view by setting its frame property.
E.g.:
CGRect menuFrame = entirePage.frame;
if (menuDisplayed)
{
menuFrame.origin.x += 81;
}
else
{
menuFrame.origin.x -= 81;
}
entirePage.frame = menuFrame;
// Do your animation here, after the view has been moved
Don't worry about the animation until you've verified that your view is being placed in the correct location. Once you've verified that that code is working correctly, then do your animation after you set the frame property.
Note that you probably don't need to use the CoreAnimation API at all, unless you're doing something complex. UIKit can do animation on its own. E.g:
[UIView beginAnimations:#"MyAnimation" context:NULL];
myFirstView.frame = ...;
myOtherView.frame = ...;
[UIView commitAnimations];