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];
Related
So, right now I have a rocket which bounces side to side across the bottom of the iphone 5 screen. I want the rocket however to stop moving side to side but to go upwards once the user touches the screen (so touching the screen makes the rocket take off).
First, in viewDidAppear I told the compiler to run the spawnRocket method after 2 seconds.
In the spawnRocket method I established the rockets image and frame, and then I added it to the view and performed a uiview animation sending the rocket to the right side of the screen. In the finished parameter in the uiview animation method I told the compiler to run the moveRocketLeft method.
In the moveRocketLeft method I did an animation which would send the rocket to the left of the screen, and in the finished parameter I ran the moveRocketRight method.
The moveRocketMethod is basically the spawnRocket method except I don't establish the rockets image and frame and add it to the view.
So after all this, I tried to implemented the -touchesBegan:withEvent: method. I tried simply running a uiview animation which changed the rockets y to off the screen upwards, and the x to whatever the x was currently at when the user touched the screen.
However, I realize that the calling the rockets frame does not return the location that the rocket looks like while animating. It really just returns what the rockets frame will be when its done animating. So, to get the frame I want should I call layer? I remember reading somewhere that layer will return the actual location of a uiimageview during an animation.
But, layer only has the property bounds and not frame, so I'm a little confused.
I also tried calling [self.view.layer removeAllAnimations]; and then running a new animation which would shoot the awkward up, but the removeAllAnimations method never worked (and anyway i don't really like the idea of using that because i heard it lags).
So anyone have any idea how i can implement touchesBegan:withEvent: method so that the rocket can take off correctly? (or if you think I have to change my whole program please feel free to help)
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#property UIImageView *rocket;
#end
#implementation ViewController
#synthesize rocket=_rocket;
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.view.layer removeAllAnimations];
// [UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^(){self.rocket.frame=CGRectMake(self.rocket.frame.origin.x, -40, 25, 40);} completion:^(BOOL finished){}];
// this didn't work :(
}
-(void)viewDidAppear:(BOOL)animated{
[self performSelector:#selector(spawnRocket) withObject:self afterDelay:2];
}
-(void)spawnRocket{
self.rocket=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"default.png"]]; //places imageview right off screen
self.rocket.frame=CGRectMake(-25, 420, 25, 40);
[self.view addSubview:self.rocket];
[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(){self.rocket.frame=CGRectMake(295, 420, 25, 40);} completion:^(BOOL finished){if (finished)[self moveRocketLeft]; }];
}
-(void) moveRocketLeft{
[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(){self.rocket.frame=CGRectMake(0, 420, 25, 40);} completion:^(BOOL finished){if (finished)[self moveRocketRight];}];
}
-(void)moveRocketRight{
[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(){self.rocket.frame=CGRectMake(295, 420, 25, 40);} completion:^(BOOL finished){if (finished)[self moveRocketLeft];}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
This should do the trick
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CALayer *rocketPresentationLayer = [self.rocket.layer presentationLayer];
self.rocket.layer.position = rocketPresentationLayer.position;
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveEaseIn|UIViewAnimationOptionBeginFromCurrentState animations:^(){self.rocket.frame=CGRectMake(self.rocket.frame.origin.x, -40, 25, 40);} completion:^(BOOL finished){}];
}
Note 1: I didn't put logic in here to check whether the user tapped the rocket again, when it is taking off vertically. You might want to add a BOOL or something to see if the user successfully hit the rocket or not, and if so, don't perform the vertical animation again.
Note 2: If in future you only want this to occur when the rocket is hit, you can use hitTest checking to call the animation conditionally.
// Get the touch
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
// See if we hit the rocket
CALayer *rocketPresentationLayer = [self.rocket.layer presentationLayer];
CALayer* hitTest = [rocketPresentationLayer hitTest:touchPoint];
// If we did, then stop animation and move upwards
if (hitTest) {
....
}
P.S: Also as a slight mention, #property UIImageView *rocket; should be changed to #property (nonatomic) UIImageView *rocket;. This is primarily for performance reasons, as I don't expect your rocket to be accessed by multiple threads (it shouldn't, it's the UI!).
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.
Im developing a word finding app.Here i want some words to fall from the top of screen of iphone.
There will be a red line at the bottom of screen.
User have to select the correct word before reaching a red line.
So how can i implement the falling effect in iphone.(i.e words falling effect).
Can anyone please help me with this.
Thanks in advance.
You can implement this using UIView animations. This sample code is all within a view controller, with a single label, fallingLabel added to the view. You can expand it to include multiple labels.
The key point is to import the QuartzCore framework. This gives you access to the presentationLayer of a view, which holds the current state of an animating view. You add the framework to your project in the project summary part of Xcode, then put the following in the top of your view controller's implementation file:
#import <QuartzCore/QuartzCore.h>
The following code animates the dropping of the label. It will increase in speed as it falls due to the UIViewAnimationOptionCurveEaseIn setting, giving you a convincing gravity effect.
self.fallingLabel.center = CGPointMake(160,50);
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{self.fallingLabel.center = CGPointMake(160,400);}
completion:nil];
Now, you need to implement touch handling. This is not done on the label, as internally it will think it is already at the bottom of the screen. You handle the touches in your view controller:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if ([self.fallingLabel.layer.presentationLayer hitTest:touchLocation])
{
NSLog(#"Label was touched");
CALayer *presLayer = self.fallingLabel.layer.presentationLayer;
[UIView animateWithDuration:0
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{self.fallingLabel.frame = presLayer.frame;}
completion:nil];
}
}
Here, what is happening is that the presentation layer is being queried to see if the touch falls within it's current bounds. If so, we then start a new animation, which starts from the current state - in this case all I have done is stop the falling of the label, but you could implement anything you like here.
If you have multiple falling labels you can try the hitTest on the presentation layer of each one until you find the one that has been touched.
try to use usual animations blocks, but run than in separate theard:
NSOperationQueue *_queue;
_queue = [[NSOperationQueue alloc] init];
SEL selector = #selector(fallWord);
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature];
[inv setTarget:self];
[inv setSelector:selector];
NSInvocationOperation *fall= [[[NSInvocationOperation alloc] initWithInvocation:inv] autorelease];
[_queue addOperation:fall];
then, implement fallWord method, where you will set an animatin of falling:
UILabel *lb = [[UILabel alloc] initWithFrame:CGRectMake(<#CGFloat x#>, <#CGFloat y#>, <#CGFloat width#>, <#CGFloat height#>)];
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
[lb setCenter:CGPointMake(<#CGFloat x#>, <#CGFloat y#>)];
} completion:nil];
then, you will need to implement touches and in method
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSUInteger touchCount = 0;
for (UITouch *touch in touches) {
[self dispatchFirstTouchAtPoint:[touch locationInView:self] forEvent:nil];
touchCount++;
}
}
check if touch coodinates inside your UILabel's frame
Cocos2d combined with box2d or chipmunk will help you achieve a falling affect (simulating gravity towards the bottom of the screen).
I will give a general plan with Cocos2d / chipmunk
Learn both.
Cocos2d - CCLabelTTF, CCLayer, CCSprite, CCTouch recognition, how to schedule a timer with cocos2d
Chipmunk - bodies, gravity, using the timer set up with cocos2d to step through the physics, calling a C static void function to update the CCLabelTTF's that are still on screen, removing those objects that aren't on screen (past the red line), and how to use the Cocos2d touch methods to verify if you hit a word or not.
ie method
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
There are great starter documents on Cocos2d's site and not so great documentation for chipmunk, but you can find tutorials on hooking that in.
I am not sure if UIKit animations can be interrupted how they will need to be in your app.
In any implementation, you will also need to ensure your view displaying the text isn't bigger than the text.
If you take the cocos2d + physics engine route to achieve your goal, you will be in good shape to learn how to make an even more complex game.
You are actually looking for AutoComplete textbox. Basically there is no such thing exist in iPhone. but with the help of UIPicker view you can provide such effect
I've created a custom UIAlertView (by subclassing it and messing around with its show function) that has some custom subviews and is of non-standard size.
It works ok when I create and display it, however, when the device is rotated, the alert rotates and then returns to its default size.
Any ideas what functions to override - or should I tweak the UIViewController?
thanks,
Peter
Not sure if force rotating the UIAlertView fits the Apple GUI guidelines, but you can rotate it by defining the status bar (status bar and UIAlertView sticks together)
application.statusBarOrientation = UIInterfaceOrientationLandscapeRight;
application.statusBarOrientation = UIInterfaceOrientationLandscapeLeft;
But UIAlertView is a UIView just like many others, so try this :
- (void)didPresentAlertView:(UIAlertView *)alertView
{
[UIView beginAnimations:#"" context:nil];
[UIView setAnimationDuration:0.1];
alertView.transform = CGAffineTransformRotate(alertView.transform, degreesToRadian(90));
[UIView commitAnimations];
}
From all of the view controllers within my application if I am processing a long running task I present the user a 'progress view'. This is a UIView that lives in my MainWindow.xib. I show (fade in) the view using an AppDelegate method...
- (void)showProgressView
{
self.progressView.hidden = NO;
[UIView animateWithDuration:1.0
animations:^(void) { self.progressView.alpha = 1.0; }];
}
When the long running task has finished I fade out the 'progress view' with the following AppDelegate method...
- (void)hideProgressView
{
[UIView animateWithDuration:1.0
animations:^(void) { self.progressView.alpha = 0.0; }
completion:^(BOOL f) { self.progressView.hidden = YES; }
}];
}
My problem is that as the progress view fades away and as the buttons/controls in the under-lying view below become visible again they ARE NOT usable (they don't respond to touch events) until the animation has fully finished and the 'progress view' is hidden.
Is there anyway for me to pass control back to the underlying view, before starting the fade out animation, so that its buttons etc do work whilst the progress view fades away?
EDIT: Things I have already tried unsuccessfully...
Using resignFirstResponder
- (BOOL)findAndResignFirstResponder:(UIView*)view
{
NSLog(#"looping %#", [view description]);
if (view.isFirstResponder)
{
[view resignFirstResponder];
return YES;
}
for (UIView *subView in view.subviews)
{
if ([self findAndResignFirstResponder: subView])
{
return YES;
}
}
return NO;
}
- (void)hideProgressView
{
// Recursively attempt to remove control from the progress view
BOOL result = [self findAndResignFirstResponder:self.progressView];
[UIView animateWithDuration:1.0
animations:^(void) { self.progressView.alpha = 0.0; }
completion:^(BOOL f) { self.progressView.hidden = YES; }
}];
}
Using endEditing
- (void)hideProgressView
{
// Attempt to remove control from the progress view
[self.progressView endEditing:YES];
[UIView animateWithDuration:1.0
animations:^(void) { self.progressView.alpha = 0.0; }
completion:^(BOOL f) { self.progressView.hidden = YES; }
}];
}
You can try sending your progress view the message resignFirstResponder. This should make the view below it the first responder, so you can use its controls.
PS: I also think that maybe your progress view could be filling the whole display; in this case, changing the first responder might not help...
EDIT: after you confirmed that your view is taking the full screen...
If your view is full screen, it is intercepting all the touches that you do (because when it is not fully hidden/transparent it is covering the views behind it). You have two options, either you make the view smaller, so that you have no overlapping, or you make so that the touches are forwarded to the view behind it.
You can do the latter in several ways, I hope that one works for you:
you can try and override in your progress view (it needs be a custom UIView), the touchesBegan method;
you can try and override the hitTest method in your progress view;
This is what I would try, e.g. in touchesBegan:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (![self viewIsDisappearing])
[self.nextResponder touchesBegan:touches withEvent:event];
}
viewIsDisappearing is a method you should implement to return YES if the animation to hide the progress view has already begun. During the animation the view is not yet hidden, so it will intercept touches, and you forward those touches to the next responder.
It is possible that you also need to override the other UIResponder's touche-related methods:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
– touchesCancelled:withEvent:
EDIT:
I have found a class of mine where I do something similar to what I am suggesting here, only, without using the nextResponder.
The idea is: SDSTransparentView is a UIView that covers the whole screen. You initialize it like this:
[[SDSTransparentView alloc] initWithContent:contentView andDelegate:delegate];
The delegate implements an SDSTransparentViewProtocol which simply contains one method:
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event outsideOfView:(UIView*)view;
When the user touches anywhere on the transparent view, it relays the touch to the delegate by calling the protocol method. I would suggest you to ignore the contentView and outsideOfView arguments (they where useful for me, but possibly not for you; you can either pass nil or, better, the view behind the progress view).
You can find the class on my github. You only need the SDSTransparentView.* files. Actually, I only suggest having a look at how the class is implemented (very short) and do the same in your progress view.
I can assure that this approach works!