UITapGestureRecognizer is ignored after moving outside view and back in - iphone

I have a UILabel as child in my UIView. I use it as status panel which slides out of the view if nothing has to be shown. I do that by simply animating it to origin.y of minus the height of the label.
As soon as a message has to be displayed, I slide the label back into the view. After a delay of a few seconds it slides back out. That works fine.
I also added a UITapGestureRecognizer to the label, so the user can dismiss the message right away without waiting for it to go away automatically.
My problem is, that the gesture recognizer is not firing as once the label moved outside the view. I initialize and add the gesture recognizer when the label is completely inside the view and visible. It works as expected the first time. But when the message comes back in, the gesture recognizer seems to be removed or disabled.
I also tried to add a gr every time the label is completely on the screen in the complete block of my animation, but that didn't help either.
Can someone explain to me what happens here and how I can make the recognizer work all the time?
If you need and further information let me know.
UPDATE
I did some further testing and I get this when I log the lblError.gestureRecognizers in the showError call:
<UITapGestureRecognizer: 0x6b153f0; state = Possible; view = <UILabel 0x6b14fa0>; target= <(action=dismissError:, target=<OptionViewController 0x686d2a0>)>>
It's exactly the same I get right after it's creation. So it's still there and I guess the touch events don't get to it.
UPDATE 2
I get a step further.
The problem seems to be that I move the label to y coordinate 0. That's probably a bug in the GestureRecognizer code because when I set it 0.1 it works!
Looks like the system "thinks" that the label is not in the view and therefore disables the touch handling or something. ^^
This solves one half of the problem but creates a new one on the other side. Now that the gesture recognizer works, the delayed move out animation don't get triggered anymore.
So I think the real problem is, that the move out animation is triggered right after the move in. Even though it is delayed, it prevents the label from receiving any kind of touch events.
The code
// add gesture recognizer (in viewDidLoad)
UITapGestureRecognizer *errorDismissGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissError:)];
[lblError addGestureRecognizer:errorDismissGesture];
// display error
- (void)showError:(NSString *)message {
[lblError setText:message];
[UIView animateWithDuration:0.5
animations:^(void) {
CGRect frame = lblError.frame;
frame.size.width = self.view.bounds.size.width;
frame.origin.y = 0;
lblError.frame = frame;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.5
delay:2.0
options:UIViewAnimationOptionCurveEaseOut
animations:^(void) {
CGRect frame = lblError.frame;
frame.origin.y = -40.0f;
lblError.frame = frame;
}
completion:^(BOOL finished) {}];
}];
}
- (void)dismissError:(UIGestureRecognizer *)sender {
[UIView animateWithDuration:0.3
animations:^(void) {
CGRect frame = lblError.frame;
frame.origin.y = -40.0;
lblError.frame = frame;
}
completion:^(BOOL finished) {}];
}
Thanks and Greets,
Thomas

I finally solved it!
It seems like the delayed move out animation somehow prevents the label from receiving any touch events. Even a UIAnimationOptionAllowUserInteraction did not help.
So I replaced the delayed animation with a timed call which makes the code even a little for readable. This is how it looks now.
- (void)showError:(NSString *)message {
[lblError setText:message];
[UIView animateWithDuration:0.5
animations:^(void) {
CGRect frame = lblError.frame;
frame.size.width = self.view.bounds.size.width;
frame.origin.y = 0.1;
lblError.frame = frame;
}
completion:^(BOOL finished) {
if (finished) {
[_moveOutTimer invalidate];
_moveOutTimer = nil;
_moveOutTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(dismissError:) userInfo:nil repeats:NO];
}
}];
}
- (void)dismissError:(UIGestureRecognizer *)sender {
[_moveOutTimer invalidate];
[UIView animateWithDuration:0.3
animations:^(void) {
CGRect frame = lblError.frame;
frame.origin.y = -40.0;
lblError.frame = frame;
}
completion:^(BOOL finished) {}];
}

Related

Block not being called and not animating in IOS 7

I am using ShakingAlertView in my app.
https://github.com/lukestringer90/ShakingAlertView
It works perfectly in IOS 6. But after i updating to IOS 7 it didnt animate and the block function for incorrect handing not being called.
Given below is the code for the initialization of shaking alert view.
currentPass = [[ShakingAlertView alloc]initWithAlertTitle:#"Enter Current Password" checkForPassword:self.pass
usingHashingTechnique:HashTechniqueMD5
onCorrectPassword:^{
isCurrentPassConfirmed = YES;
[self._accountSource willScrollToTop];
self.password.text = #"";
[self.password becomeFirstResponder];
} onDismissalWithoutPassword:^{
//NSLog(#"hi");
[self showFailedPasswordAlert];
}];
currentPass.alertViewStyle = UIAlertViewStyleSecureTextInput;
[currentPass show];
Below is the method to animate for shake effect.This is invoked correctly but there is no effect.
- (void)animateIncorrectPassword {
// Clear the password field
_passwordField.text = nil;
// Animate the alert to show that the entered string was wrong
// "Shakes" similar to OS X login screen
CGAffineTransform moveRight = CGAffineTransformTranslate(CGAffineTransformIdentity, 20, 0);
CGAffineTransform moveLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, -20, 0);
CGAffineTransform resetTransform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, 0);
[UIView animateWithDuration:0.1 animations:^{
// Translate left
self.transform = moveLeft;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 animations:^{
// Translate right
self.transform = moveRight;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 animations:^{
// Translate left
self.transform = moveLeft;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 animations:^{
// Translate to origin
self.transform = resetTransform;
}];
}];
}];
}];
}
Please help me.
iOS7 does not allow you to customize the UIAlertview.
Better create the custom view subclass of UIView which is draw the
view programmatically using - (void)drawRect:(CGRect)rect method.
And create one more container class(inherited from NSObject) which
is used to create and bind your title/password and OK buttons
with your custom delegate property into your customized alert
view.So that we can implement our custom delegate method as like
clickedButtonAtIndex method.
Upto my knowledge there is no changes in block/animation in iOS7.
Or refer this link https://github.com/wimagguc/ios-custom-alertview
The layout of UIAlertView changed drastically is iOS 7, making it almost impossible to customise and alter. You're going to have to come up with a new solution.

UIScrollView with fadeIn/fadeOut effect

I've scrollview with page control and I want to make the subview of my scrollview fadeIn/fadeOut when I scroll to or from the next page. I found that I can use contentOffset to make this effect but I couldn't make it.
In fact, I'm newbie and I wish If there is any tutorial that can help. thank you.
Assuming you hold an array of your view controllers in self.viewControllers indexed according to the UIPageControl:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat diffFromCenter = ABS(scrollView.contentOffset.x - (self.pageControl.currentPage)*self.view.frame.size.width);
CGFloat currentPageAlpha = 1.0 - diffFromCenter/self.view.frame.size.width;
CGFloat sidePagesAlpha = diffFromCenter/self.view.frame.size.width;
//NSLog(#"%f",currentPageAlpha);
[[[self.viewControllers objectAtIndex:self.pageControl.currentPage] view] setAlpha:currentPageAlpha];
if (self.pageControl.currentPage > 0)
[[[self.viewControllers objectAtIndex:self.pageControl.currentPage-1] view] setAlpha:sidePagesAlpha];
if (self.pageControl.currentPage < [self.viewControllers count]-1)
[[[self.viewControllers objectAtIndex:self.pageControl.currentPage+1] view] setAlpha:sidePagesAlpha];
}
You might check out this tutorial on view animation:
Uiview-tutorial-for-ios-how-to-use-uiview-animation
To achieve the effect you are looking for you can use something like this:
ScrollView delegate method to detect scrolling (if your only paging)
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UIView* subView = [scrollView.subviews lastObject]; //Or however you access your subview
[UIView animateWithDuration:1.0f animations:^{
subView.alpha = 0.0f;
} completion:^(BOOL finished){
[UIView animateWithDuration:1.0f animations:^{
subView.alpha = 1.0f;
}];
}];
}
This will cause your subview to smoothly fade out and in within the span of 2.0 seconds. You should a bit of reading about this animation blocks though as they can be a little tricky. For instance I had nest the second animation block after the first had complete because the actual code within them is handled immediately and the animation simply takes place on the View side of things.
Hope this helps!
You can fade it to zero, change the contentOffset without animation, and fade it back to alpha with 1.0:
- (IBAction)didChangePageView:(id)sender
{
[UIView animateWithDuration:0.25
animations:^{
self.scrollView.alpha = 0.0;
}
completion:^(BOOL finished) {
[self.scrollView setContentOffset:CGPointMake(0, self.scrollView.frame.size.height * self.pageViewControl.currentPage) animated:NO];
[UIView animateWithDuration:0.25 animations:^{
self.scrollView.alpha = 1.0;
}];
}];
}
With Swift 2.0, assuming you hold an array of your subViews in myViewsArray:
func scrollViewDidScroll(scrollView: UIScrollView) {
//Fade in/out effect while scrolling
for (index,subView) in myViewsArray.enumerate() {
label.alpha = 1 - abs(abs(scrollView.contentOffset.x) - subView.frame.width * CGFloat(index)) / subView.frame.width
}
}

Problems animating UIView alpha

I'm trying to apply a fade to an UIView I created programmatically on the top of another.
[UIView animateWithDuration:0.5 animations:^(void) {
[self.view setAlpha:0];
}
completion:^(BOOL finished){
[self.view removeFromSuperview];
}];
The finished event is called properly after exactly 0.5 seconds, but I don't see any fade (I should see the UIView on the bottom).
If instead of using the alpha, I move away the UIView it works (I see the bottom UIView while the top UIView slides away), so it seems to be a problem related to alpha, but I can't figure out what's wrong!
[UIView animateWithDuration:0.5 animations:^(void) {
CGRect o = self.view.frame;
o.origin.x = 320;
self.view.frame = o;
}
completion:^(BOOL finished){
[self.view removeFromSuperview];
}];
I used alpha animations previously and they works in this way usually...
Try setting opaque to NO explicitly. I had the same problem and setting that solved my problem. Apparently, opaque views just don't seem to play well with alpha.
Credit goes to Hampus Nilsson's comment though.
I experience same issue in my case its because of thread
So i end up with write animation block in main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:2 delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{
View.alpha = 0.0f;
} completion:^(BOOL finished) {
}];
});
[UIView animateWithDuration:0.6 delay:0 options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[self.view setAlpha:0];
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
}];
This will work right, and your fading will be nicer, because of options:UIViewAnimationOptionCurveEaseInOut.
I ran into the exact problem. In my case, I wanted the view to be hidden at first, so I set hidden to true. I thought changing the alpha value changes hidden property automatically, but that wasn't the case.
So, if you want the view to be hidden at first, set its alpha to 0.
I had exactly the same problem, and none of the suggestions worked for me. I overcame the problem by using the layer opacity instead.
This is the code for showing the layer (using Xamarin, but you'll get the idea):
//Enter the view fading
zoomView.Layer.Opacity = 0;
UIApplication.SharedApplication.KeyWindow.RootViewController.View.Add(zoomView);
UIView.AnimateNotify(
0.15, // duration
() => { zoomView.Layer.Opacity = 1.0f },
(finished) => { }
);
And this is for fading out the same zoomView
UIView.AnimateNotify(
0.15, // duration
() => { zoomView.Layer.Opacity = 0; },
(finished) =>
{
if (finished)
{
zoomView.RemoveFromSuperview();
}
}
);
One more piece of the puzzle. When you're animating constraints, you can set the new constraint constants outside of an animation block and then call layoutIfNeeded inside the animation.
With view alphas, these need to be set directly inside the animation block.
Change this:
//setup position changes
myConstraint.constant = 10
myOtherConstraint.constant = 10
//whoops, alpha animates immediately
view.alpha = 0.5
UIView.animate(withDuration: 0.25) {
//views positions are animating, but alpha is not
self.view.layoutIfNeeded()
}
to
//setup position changes
myConstraint.constant = 10
myOtherConstraint.constant = 10
UIView.animate(withDuration: 0.25) {
view.alpha = 0.5
self.view.layoutIfNeeded()
}

iphone development: how to create an animation by using NSTimer

In my app I have this code in viewWillAppear. It simply animates random object from top of the screen to the bottom. The problem is I need to detect collision and what I have learnt so far is it is impossible to retrieve the coordinates at any time for an animation. so how can I implement it by using NSTimer? or shall I use NSTimer? I could not figure it out. Any clue will be appreciated.
-(void)viewWillAppear:(BOOL)animated
{
for (int i = 0; i < 100; i++) {
p = arc4random_uniform(320)%4+1;
CGRect startFrame = CGRectMake(p*50, -50, 50, 50);
CGRect endFrame = CGRectMake(p*50, CGRectGetHeight(self.view.bounds) + 50,
50,
50);
animatedView = [[UIView alloc] initWithFrame:startFrame];
animatedView.backgroundColor = [UIColor redColor];
[self.view addSubview:animatedView];
[UIView animateWithDuration:2.f
delay:i * 0.5f
options:UIViewAnimationCurveLinear
animations:^{
animatedView.frame = endFrame;
} completion:^(BOOL finished) {
[animatedView removeFromSuperview];
}];
}
I would ditch NSTimer for CADisplayLink to send a message to your target. NSTimer can cause stuttering animations since it doesn't synchronize with the device's screen refresh rate. CADisplayLink does exactly that and makes your animations run like butter.
Documentation:
http://developer.apple.com/library/ios/#documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html
Ultimately, using CADisplayLink looks something like this:
- (id) init {
// ...
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(update:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// ...
}
- (void) update {
[myView updatePositionOrSomethingElse];
}
Your statement:
is it is impossible to retrieve the coordinates at any time for an
animation
seems incorrect did you check this?
It looks like you can use the CALayer's presentationLayer property to extract coordinates info during an animation:
CGRect movingFrame = [[yourView.layer presentationLayer] frame];
I will use this info to check time to time if there is a collision during the animation. So I will use the timer to check the collision status not for animating the views.
You can have your objects animate in small increments (in a loop may be). Every time you complete your animation, you can get the coordinates.
I think you could use the beginAnimations - commitAnimations syntax in a for cycle it could count for eg. till 10, so after each cycle you can check for collision
CGRect incrementedViewFrame;
for(int i = 0; i < 10; ++i)
{
incrementedViewFrame = CGRectMake(/*calculate the coords here*/);
if(collision)
{
//do stuff
}
else
{
//do an other animation cycle
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.1f];
[[self viewToAnimate]setFrame:incrementedViewFrame];
[UIView commitAnimations];
}
}
I hope that helps!

UIView animates slide in but not slide out using block animation method

I have a UIView called geopointView. I want to animate slide in this UIview on main view frame when a button (myButton) is pressed. If myButton is pressed again UIview should slide out of frame. myButton calls following method "optionsCalled"
-(void) optionsCalled
{
if (![[self.view subviews] containsObject:geopointView] &&checkForGeopointViewAnimation ==0) {
[self.view addSubview:geopointView];
geopointView.frame = CGRectMake(0,430,320,30);
[UIView animateWithDuration:0.3f
animations:^{
geopointView.frame = CGRectMake(0, 350, 100, 80);
}
completion:^(BOOL finished){
checkForGeopointViewAnimation = 1 ;
}];
}
if ([[self.view subviews] containsObject:geopointView] && checkForGeopointViewAnimation ==1)
{
geopointView.frame = CGRectMake(0, 350, 100, 80);
[UIView animateWithDuration:0.3f
animations:^{
geopointView.frame = CGRectMake(0,430,320,30);
}
completion:^(BOOL finished){
checkForGeopointViewAnimation = 0 ;
}];
[geopointView removeFromSuperview];
}
}
here checkForGeopointViewAnimation is a global variable defined to ensure slide in and out are not called at one time.
Problem with this code is that UIview slides in animatedly but it does not animate slide out action. it just disappears from main frame. Do I need to use any time here so that there is delay in calling add and remove UIview functions?
Thanks for any help in advance
Try [geopointView removeFromSuperview]; after the completion of the animation
Maybe you need to call [geopointView removeFromSuperview]; in your completion block instead?