Animating custom UIView from off screen make its subviews not responding - ios

I created a small custom UIView with a UILabel and a UIButton, this custom view is a banner to display at the top of the current view controller.
I load the view layout from a nib file and use a method from the custom view to display it with an animation, and the view will hide after a specific amount of time. Like this.
- (void)displayBannerInViewController:(UIViewController *)vc
{
CGFloat originY = 0;
if (vc.navigationController != nil) {
originY += 20 + vc.navigationController.navigationBar.bounds.size.height - self.bounds.size.height;
}
self.frame = CGRectMake(0,
originY,
[UIScreen mainScreen].bounds.size.width,
self.bounds.size.height);
if (vc.navigationController != nil) {
[vc.navigationController.view insertSubview:self atIndex:1];
} else {
[vc.view.window insertSubview:self atIndex:1];
}
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, self.bounds.size.height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 delay:self.duration options:0 animations:^{
self.frame = CGRectOffset(self.frame, 0, -self.bounds.size.height);
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}];
}
I set the action for the button inside the banner with this
[self.actionButton addTarget:self
action:#selector(executeActionBlock)
forControlEvents:UIControlEventTouchUpInside];
After animation showing the banner, and before is hidden, no matter how many times I tap on the button, the executeActionBlock method is never called.
I made a test setting the initial frame of the banner to origin (0, 0) and without animation and then the button worked fine. So, I don't know if a problem of the animation or because the original frame of the banner is in a non visible position. BTW, is important for the banner to not be visible because on the app is showing from under the navigation bar.
Thanks

You've written your code so that you run a series of animations, where the view is on-screen during the delay of the last animation. By default user interaction is disabled on views while they are being animated. Try passing UIViewAnimationOptionAllowUserInteraction in options for all your nested animations.
https://developer.apple.com/reference/uikit/uiviewanimationoptions/uiviewanimationoptionallowuserinteraction?language=objc

Well, it seems I should keep looking more because I found this question with the same problem. I tried the solution there and now is working.
The problem is that linking animations is not a good idea for user interaction so I used a NSTimer instead, like this.
- (void)displayBannerInViewController:(UIViewController *)vc
{
CGFloat originY = 0;
if (vc.navigationController != nil) {
originY += 20 + vc.navigationController.navigationBar.bounds.size.height - self.bounds.size.height;
}
self.frame = CGRectMake(0,
originY,
[UIScreen mainScreen].bounds.size.width,
self.bounds.size.height);
if (vc.navigationController != nil) {
[vc.navigationController.view insertSubview:self atIndex:1];
} else {
[vc.view.window insertSubview:self atIndex:1];
}
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, self.bounds.size.height);
} completion:^(BOOL finished) {
[NSTimer scheduledTimerWithTimeInterval:self.duration target:self selector:#selector(hideBanner) userInfo:nil repeats:NO];
}];
}
- (void)hideBanner
{
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, -self.bounds.size.height);
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}
And now works like a charm.

Related

UIView not animated properly using UIView Animation

I am working on UIView Animation. I want to move a view upside while i scroll up and should move down when i scroll down.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint scrollVelocity = [_collectionViewLeaderboard.panGestureRecognizer velocityInView:_collectionViewLeaderboard.superview];
if (scrollVelocity.y > 0.0f){
NSLog(#"going down");
[UIView animateWithDuration:0.3f
animations:^ {
_headerview.frame = CGRectMake(0, 0, _headerview.frame.size.width, _headerview.frame.size.height);
_headerviewSecond.frame = CGRectMake(0, _headerview.frame.size.height, _headerviewSecond.frame.size.width, _headerviewSecond.frame.size.height);
self.collectionViewLeaderboard.frame = CGRectMake(self.view.frame.origin.x, _headerviewSecond.frame.size.height+_headerview.frame.size.height, self.view.frame.size.width, self.view.frame.size.height);
frameconditon = _headerview.frame;
_viewOptions.frame = CGRectMake(_viewOptions.frame.origin.x,_headerview.frame.size.height+_headerviewSecond.frame.size.height+30, _viewOptions.frame.size.width, _viewOptions.frame.size.height);
} completion:^ (BOOL completed) {
}];
}
else if (scrollVelocity.y < 0.0f){
NSLog(#"going up");
[UIView animateWithDuration:5.3 animations:^{
_headerview.frame = CGRectMake(_headerview.frame.origin.x, -(_headerview.frame.size.height), _headerview.frame.size.width, _headerview.frame.size.height);
_headerviewSecond.frame = CGRectMake(0, (_headerview.frame.size.height)-40, _headerviewSecond.frame.size.width, _headerviewSecond.frame.size.height);
self.collectionViewLeaderboard.frame = CGRectMake(self.view.frame.origin.x, _headerviewSecond.frame.size.height, self.view.frame.size.width, self.view.frame.size.height);
frameconditon = _headerviewSecond.frame;
_viewOptions.frame = CGRectMake(_viewOptions.frame.origin.x,_headerviewSecond.frame.size.height+20, _viewOptions.frame.size.width, _viewOptions.frame.size.height);
} completion:^(BOOL finished) {
}];
}
}
I believe its very simple thing if you want to move the view up and down.
Because If you add view in scroll view you can automatically get the view up and down along with the scroll. You just need to set "contentSize" property of scrollView.
If its not the case, you are trying to get panGesture of UICollectionView then you can not get in "scrollViewDidScroll" method.
Here is the useful tutorial on UIPanGestureRecognizer with UICollectionView. Hope so it will help.
https://uncorkedstudios.com/blog/using-uigesturerecognizers-with-uicollectionviews

UIPickerView Animate on and off screen behavior

This is a continuation of this question
CGRectMake : How To Calculate X and Y For UIPickerView Animation?
answered by https://stackoverflow.com/users/2315974/danypata
I have a picker view that I want to animate on and off screen on a button press and using the code in the previous question/answer I have got it to animate on screen the first time the button is pressed.
the second tome the button is pressed it just disappears and then the third time it is pressed it animates to the wrong place...please help
the code
if (self.pickerSort.hidden == NO) {
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.pickerSort.frame = CGRectMake(0,self.view.frame.size.height
+ self.pickerSort.frame.size.height,-self.pickerSort.frame.size.width,-self.pickerSort.frame.size.height);
}
completion:^(BOOL finished) {
}];
self.pickerSort.hidden = YES;
// [self performSelector:#selector(hidePicker) withObject:nil afterDelay:1];
} else if (self.pickerSort.hidden == YES) {
self.pickerSort.hidden = NO;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.pickerSort.frame = CGRectMake(0 ,
self.view.frame.size.height
- self.pickerSort.frame.size.height,
self.pickerSort.frame.size.width,
self.pickerSort.frame.size.height);
}
completion:^(BOOL finished) {
}];
[self.view bringSubviewToFront:self.pickerSort];
Behavior in images - button press animates onto screen beautifully
Second Press it disappears with no animation
Third Press it animates to here
Any help would be great...functionality I am looking for is how to put the picker view back on an animation so the 3rd press is the same as the first
Please try to use the below code.
self.pickerSort.hidden = NO;
NSTimeInterval duration = 0.5;
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
CGRect pickerFrame = self.pickerSort.frame;
// currently picker is off the screen, show it.
if (pickerFrame.origin.y == self.view.frame.size.height) {
// set frame to show picker
pickerFrame.origin.y = self.view.frame.size.height - self.pickerSort.frame.size.height;
} else {
// set frame to hide picker
pickerFrame.origin.y = self.view.frame.size.height;
}
self.pickerSort.frame = pickerFrame;
}
completion:^(BOOL finished) {
self.pickerSort.hidden = YES;
}];
After playing around with this and learning the ins and outs of frames and CGRects, I achieved this by using the following code
if (pickerSort.hidden == YES) {
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
pickerSort.hidden = NO;
visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
[self.tableStations addSubview:visualEffectView];
pickerSort.frame = CGRectMake(0,0,originalPickerFrame.size.width,originalPickerFrame.size.height);
visualEffectView.frame = CGRectMake(0,0,originalPickerFrame.size.width,originalPickerFrame.size.height - 64);
}
completion:^(BOOL finished) {
self.navigationController.navigationItem.leftBarButtonItem.enabled = NO;
}];
}
else
{
visualEffectView.hidden = YES;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
pickerSort.frame = CGRectMake(0,-originalPickerFrame.size.height,0,0);
}
completion:^(BOOL finished) {
self.navigationController.navigationItem.leftBarButtonItem.enabled = YES;
pickerSort.hidden = YES;
}];
}
I set the original frame size in viewDidLoad like so
pickerSort = [[UIPickerView alloc]initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
originalPickerFrame = pickerSort.frame;
pickerSort.frame = CGRectMake(0,-originalPickerFrame.size.height,0, 0);
Hope this can help somebody in the future

How to animate deletion of a label?

I have a view controller that have a scroll view that bounce horizontally , and in this scroll view I have a label.
I can now hold the label and scroll it down, and if I release it will bounce up back.
What I want is that: When I scroll the view y coordinate (using myScrollView.contentOffset.y) to some value, lets say -33 and under I can release my fine and the label will animate to the bottom of the screen and disappear, and now I can set the label to be a new value, and It will animate from top to the label original position.
Here a photo of how the view controller looks like:
And this is the relevant method I already implemented (powered by #rebello95):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.myScrollView.contentOffset.y <= -73) {
[UIView animateWithDuration:0.3 animations:^{
self.homeLabel.alpha = 0.0;
} completion:^(BOOL finished) {
[self.homeLabel removeFromSuperview];
self.homeLabel = nil;
}];
}
NSLog(#"%f", self.myScrollView.contentOffset.y);
}
Now I want it to slide to the bottom of the page and fade.
thanks!
EDIT: The animation now moves the label to the bottom of the view controller then fades it out.
You can use an animation block to move the label, then put another block inside the completion block to fade out the label, then remove it after the animation completes.
Example:
[UIView animateWithDuration:0.3 animations:^{
[self.myLabel setFrame:CGRectMake(self.myLabel.frame.origin.x, self.view.frame.size.height - self.myLabel.frame.size.height, self.myLabel.frame.size.width, self.myLabel.frame.size.height)];
self.labelRemoving = YES;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
self.myLabel.alpha = 0.0;
} completion:^(BOOL finished) {
[self.myLabel removeFromSuperview];
self.myLabel = nil;
self.labelRemoving = NO;
}];
}];
Sidenote: You should probably be using <= instead of == in your if statement to achieve your desired results. In addition, you may want to set a flag to indicate that your label is being removed (since that method will inevitably be called multiple times). Something like this:
//.h
#property (nonatomic, assign) BOOL labelRemoving;
//.m
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.myScrollView.contentOffset.y <= -33 && !self.labelRemoving) {
[UIView animateWithDuration:0.3 animations:^{
[self.myLabel setFrame:CGRectMake(self.myLabel.frame.origin.x, self.view.frame.size.height - self.myLabel.frame.size.height, self.myLabel.frame.size.width, self.myLabel.frame.size.height)];
self.labelRemoving = YES;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
self.myLabel.alpha = 0.0;
} completion:^(BOOL finished) {
[self.myLabel removeFromSuperview];
self.myLabel = nil;
self.labelRemoving = NO;
}];
}];
}
NSLog(#"%f", self.myScrollView.contentOffset.y);
}

UISegementedControl reverts back to original frame after touch

Every time I click on my UISegementedControl it snaps back to its original frame. I can see it barely through my translucent toolbar.
I have a UIViewController with a UITableView, and UIToolBar like this:
There is a UISegmentedControl hidden just below the table view, behind the toolbar:
The Filter button calls the 'onFilterButtonPressed' method
- (IBAction)onFilterButtonPressed:(id)sender
{
if(self.filterBar.hidden){
[self showFilterBar];
} else {
[self hideFilterBar];
}
}
- (void)hideFilterBar
{
CGRect filterBarFrame = CGRectMake(0, self.view.frame.size.height+(self.filterBar.frame.size.height+1), self.filterBar.frame.size.width, self.filterBar.frame.size.height);
CGRect tableViewFrame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y,self.tableView.frame.size.width, self.tableView.frame.size.height+(self.filterBar.frame.size.height+1));
[UIView animateWithDuration:0.3 animations:^{
[self.filterBar setFrame:filterBarFrame];
[self.tableView setFrame:tableViewFrame];
} completion:^(BOOL finished) {
self.filterBar.hidden = YES;
}];
}
- (void)showFilterBar
{
CGRect filterBarFrame = CGRectMake(0, self.view.frame.size.height-(self.filterBar.frame.size.height+1), self.filterBar.frame.size.width, self.filterBar.frame.size.height);
CGRect tableViewFrame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y,self.tableView.frame.size.width, self.tableView.frame.size.height-(self.filterBar.frame.size.height+1));
self.filterBar.hidden = NO;
[UIView animateWithDuration:0.3 animations:^{
[self.tableView setFrame:tableViewFrame];
[self.filterBar setFrame:filterBarFrame];
}];
}
This is because of auto layout. With that turned on (which it is by default), you should do any positioning or resizing of views by modifying constraints, not setting frames.

Allowing one method call at a time to a category method ios (#synchronized)

I have a UIViewController and a Category for adding methods to the UIViewController. There is a method in the category:
#implementation UIViewController (AlertAnimationsAndModalViews)
-(void)someAddedMethod
{
UIView *someView;
//do some animation with the view that lasts 3 seconds
//remove the view and return
}
And in any view controller i can call this method
[self someAddedMethod];
However, i only want to allow this method to run one at a time. For example, if i make two calls one after the other
[self someAddedMethod];//call1
[self someAddedMethod];//call2
i want the second call to wait until the first call has completed. I understand that UIView animationWithduration... is run in a seperate thread, and seeing as i cant create iVars in the category i cant really use #synchronized(someObject)..
Any advice?
Thanks in advance!
EDIT
The method looks like this:
-(void)showTopBannerWithHeight:(CGFloat)height andWidth:(CGFloat)width andMessage:(NSString *)message andDuration:(CGFloat)duration
{
UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -height, width, height)];
[self.view.superview addSubview:messageLabel];
[UIView animateWithDuration:0.5
delay:0
options: UIViewAnimationOptionBeginFromCurrentState
animations:^{
messageLabel.frame = CGRectMake(0, 0, SCREEN_WIDTH, height);
}
completion:^(BOOL finished){
[UIView animateWithDuration: 0.5
delay:duration
options: UIViewAnimationOptionBeginFromCurrentState
animations:^{
messageLabel.frame = CGRectMake(0, -height, SCREEN_WIDTH, height);
}
completion:^(BOOL finished){
[messageLabel removeFromSuperview];
}];
}];
}
So i show a "banner" from the top of the screen, wait for a duration (CGFloat) then slide the view out of the screen and remove. As this is in a category i can't add instance variables..
so what i want to achieve is that if more than one call to this method is made, i want the first call to execute without waiting, but each call after that to wait until the previous call has finished.
If its just about the animations you may check if ([someView.layer animationForKey:#"sameKeyAsOnCreation"] == nil). Than you will only add an animation, if it is not currently runnning.
You could also use associated objects to store the state on your own (animation running / not running).
Assuming you want to start next animation after previous one has finished.
This way you can use some shared NSMutableArray* _animationQueue storage:
-(void)someAddedMethod
{
NSTimeInterval duration = 3.0;
void (^animationBlock)() = ^{
//do some animations here
self.view.frame = CGRectOffset(self.view.frame, 40, 0);
};
__block void (^completionBlock)(BOOL) = ^(BOOL finished){
[_animationQueue removeObjectAtIndex:0];
if([_animationQueue count]>0) {
[UIView animateWithDuration:duration animations:_animationQueue[0] completion:completionBlock];
}
};
[_animationQueue addObject:animationBlock];
if([_animationQueue count]==1) {
[UIView animateWithDuration:duration animations:animationBlock completion:completionBlock];
}
}
Note, you don't need any #synchronized features since everything goes on main thread.
UPDATE: the code below does exactly you need:
-(void)showTopBannerWithHeight:(CGFloat)height andWidth:(CGFloat)width andMessage:(NSString *)message andDuration:(CGFloat)duration
{
static NSMutableArray* animationQueue = nil;
if(!animationQueue) {
animationQueue = [[NSMutableArray alloc] init];
}
void (^showMessageBlock)() = ^{
UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, height)];
messageLabel.text = message;
[self.view.superview addSubview:messageLabel];
[UIView animateWithDuration: 0.5
delay:duration
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
messageLabel.frame = CGRectOffset(messageLabel.frame, 0, -height);
}
completion:^(BOOL finished){
[messageLabel removeFromSuperview];
[animationQueue removeObjectAtIndex:0];
if([animationQueue count]>0) {
void (^nextAction)() = [animationQueue objectAtIndex:0];
nextAction();
}
}];
};
[animationQueue addObject:showMessageBlock];
if([animationQueue count]==1) {
showMessageBlock();
}
}
Try to use this one.And used self in #synchronized directive.
- (void)criticalMethod {
#synchronized(self) {
// Critical code.
}
}
Note: The #synchronized() directive takes as its only argument any Objective-C object, including self. This object is known as a mutual exclusion semaphore or mutex. It allows a thread to lock a section of code to prevent its use by other threads.

Resources