for my iOS app I created a custom UIView that has the function of "Custom Notifications".
Everything works fine, but I have a problem, I wish the notification to appear only once. In a nutshell my Case Study is that if I click several times the button that brings up the notification latter overlaps indefinitely, instead I would (perhaps through the use of a Boolean) be able to bring up a one-time notification also if the button is pressed several times ...
Could you advise the best way to achieve this?
The custom view is presented in the external view controller in this way
-(IBAction)loginUser:(id)sender {
UTAlertView *alert = [[UTAlertView alloc] initWithTitle:#"Attenzione" message:#"Tutti i campi sono obbligatori" ];
alert.alertViewType = UTAlertViewTypeWarning;
[alert presentAlert];
[self.view addSubview:alert];
}
Custom UIView Class for Notification - Implementation file
#interface UTAlertView ()
#property (nonatomic, assign) BOOL alertActive;
#end
#implementation UTAlertView
#synthesize alertIcon;
#synthesize titleLabel, messageLabel;
#synthesize alertView;
#synthesize alertActive;
-(id)initWithTitle:(NSString*)title message:(NSString *)message {
[self initializeStringElementAlertView:title message:message];
return self;
}
-(void)initializeStringElementAlertView:(NSString *)title message:(NSString *)message {
alertView = [self initWithFrame:CGRectMake(0, [UIScreen mainScreen].bounds.size.height +100, kViewSize_W, kViewSize_H)];
[UTAlertElement alertTitle:titleLabel withString:title andAddSubview:self];
[UTAlertElement alertMessage:messageLabel withString:message andAddSubview:self];
alertIcon = [UTAlertElement iconAlertViewInView:self];
}
-(void)presentAlert {
if (!alertActive) {
alertActive =YES;
[UIView animateWithDuration:.3
animations:^{
[self bounce:1] ;
self.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height -60, [UIScreen mainScreen].bounds.size.width, kViewSize_H);
} completion:^(BOOL finished) {
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(setHideAnimation)];
tapGesture.cancelsTouchesInView = NO;
[self setUserInteractionEnabled:YES];
[self addGestureRecognizer:tapGesture];
[self performSelector:#selector(setHideAnimation) withObject:self afterDelay:3];
}];
}
}
-(void)setHideAnimation {
[UIView animateWithDuration:.3
animations:^{
self.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height +100, [UIScreen mainScreen].bounds.size.width, kViewSize_H);
} completion:^(BOOL finished) {
alertActive =NO;
}];
}
If you simply just want your code to execute only, then have a look at dispatch_once here.
Basically you can just do:
static dispatch_once_t once;
dispatch_once(&once, ^ {
// Your code to be executed only once.
});
If you want to be able to open the notification multiple times, but not at the same time, you have multiple options, including:
The singleton pattern: I would not use this in this case as this would mean that you won't be able to have multiple instances of your notification type.
Blocks or delegate: Having a completion block or using the delegate pattern could also be an option. However, I think that this approach would leave it up to the consumer of the notification class to handle the logic of when it should be possible to show the notification. This might be what you want (if you have different logic on different views), but I don't think so.
These could be good approaches, but in your case I would simply go with some basic state handling, as you suggest yourself. So, I would probably do something like this:
Step 1: Add the BOOL property to your notification class
#property (nonatomic, assign) BOOL isVisible
Step 2: Add state handling to your show and hide methods
-(void)presentAlert {
//PRESENT THE ALERT
if (!self.isVisible) {
self.isVisible = YES;
[UIView animateWithDuration:.3
animations:^{
[self bounce:1];
self.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height - 60, [UIScreen mainScreen].bounds.size.width, kViewSize_H);
}
completion:^(BOOL finished) {
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(setHideAnimation)];
tapGesture.cancelsTouchesInView = NO;
[self setUserInteractionEnabled:YES];
[self addGestureRecognizer:tapGesture];
[self performSelector:#selector(setHideAnimation) withObject:self afterDelay:3];
}];
}
}
-(void)setHideAnimation {
//REMOVE THE ALERT
[UIView animateWithDuration:.3
animations:^{
self.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height +100, [UIScreen mainScreen].bounds.size.width, kViewSize_H);
}
completion:^(BOOL finished) {
self.isVisible = NO;
}];
}
I haven't really tested it, but it should get you started. Also, you might want to look into avoiding retain cycles.
Remember that when using this approach you should not create the notification every time the user triggers the notification. You probably want to do something like:
#property (nonatomic, strong) UTAlertView *loginAlert;
And then when you setup the view (e.g. in viewDidLoad or loadView) you can create the notification:
- (void)viewDidLoad {
[super viewDidLoad];
self.loginAlert = [[UTAlertView alloc] initWithTitle:#"Attenzione" message:#"Tutti i campi sono obbligatori" ];
self.loginAlert.alertViewType = UTAlertViewTypeWarning;
}
And of course, then in your method that is responsible of showing the alert would do something like:
-(IBAction)loginUser:(id)sender {
[self.loginAlert presentAlert];
[self.view addSubview:self.loginAlert];
}
I think a single instance (Singleton) alert will solve your problem. Use the singleton pattern to avoid multiple instance of alert view appearing.
You can use GCD dispatch_once.
For example in init method of your view create dispatch_once_t dispatch_flag and then:
dispatch_once( & dispatch_flag, ^{
// your presentation code goes here
} );
Related
How would one present a UIViewController (from Storyboard say) that is modal, and slides up from the bottom of the presenting view controller. Requirements would be:
slides up from bottom, with widths aligning with the width of the presenting view controller
does NOT take up whole screen or whole parent presenting view controller (rather only is as high as required to show itself)
can be shown within the context of a view controller which doesn't take the whole screen
I do not use storyboards so I wrote it all out. You can copy paste this into a brand new project and run it to see it working.
Your PresentingController needs to conform to two things. The first protocol is: UIViewControllerTransitioningDelegate which allows the controller to provide a custom presenter (namely itself in our case below). Whatever you return here (be it self, or some other object) needs to conform to UIViewControllerAnimatedTransitioning and provide the custom animations. For this self-contained example, I chose the current viewController to be the presenter and animator.
Next, it needs to conform to protocol: UIViewControllerAnimatedTransitioning which provides the custom animation for any presenting or dismissing controllers.
In other words, when we present or dismiss a viewController, animateTransition from the UIViewControllerAnimatedTransitioning protocol will be called to determine how the child controller should animate into perspective or dismiss from the view-port.
Example (With Transition Animation):
//
// ViewController.m
// SO
//
// Created by Brandon T on 2017-01-23.
// Copyright © 2017 XIO. All rights reserved.
//
#import "ViewController.h"
//Some view controller that will be presented modally.
//I have coloured it red.
#interface ModalController : UIViewController
#end
#implementation ModalController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
#end
//The view controller that will present or dismiss some other view controller modally.
//I have coloured it white.
#interface ViewController () <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning>
#property (nonatomic, assign) bool presentingModalController;
#property (nonnull, nonatomic, strong) ModalController *modalController;
#property (nonnull, nonatomic, strong) UIButton *button;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
//For this example, I add a button to present and dismiss the redViewController.
self.button = [[UIButton alloc] initWithFrame:CGRectMake(15, self.view.center.y - 100, self.view.frame.size.width - 30, 45)];
[self.button setTitle:#"Present" forState:UIControlStateNormal];
[self.button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.button setBackgroundColor:[UIColor lightGrayColor]];
[self.button.layer setCornerRadius:5.0];
[self.button addTarget:self action:#selector(onButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.button];
//Create the redViewController and set its transitioning delegate to self (this controller will be providing the animation and presenter).
//We also set the style to OverFullScreen because we don't want this controller to disappear.
//When a view controller is presented, the one that presented it usually disappears or gets removed from the hierarchy until the child is dismissed. In the case of alerts, or controllers that need to display OVER the current one, we need to set the modalPresentationStyle.
self.modalController = [[ModalController alloc] init];
self.modalController.transitioningDelegate = self;
self.modalController.modalPresentationStyle = UIModalPresentationOverFullScreen;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)onButtonClicked:(UIButton *)button {
if (self.modalController.view.window == nil) {
[self presentViewController:self.modalController animated:YES completion:nil];
[self.button setTitle:#"Dismiss" forState:UIControlStateNormal];
//not a good idea but meh.. I need to keep this example short.
[self.view.window addSubview:self.button];
}
else {
[self.modalController dismissViewControllerAnimated:YES completion:nil];
[self.button setTitle:#"Present" forState:UIControlStateNormal];
[self.view addSubview:self.button];
}
}
//Custom Animations and Presenters.
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
self.presentingModalController = true; //We are presenting the controller.
return self; //Who is animating it? We are.
}
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
self.presentingModalController = false; //We are dismissing the view controller.
return self; //Who animated it? We did.
}
//How fast should it present? I chose 0.5 seconds.
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
return 0.5;
}
//The actual animation code.
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
if (self.presentingModalController) {
//If we are presenting, we need to add the new controller's view as a sub-view.
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//We need a starting frame for the animation.
CGRect startingFrame = transitionContext.containerView.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
//We need an end frame for the animation.
CGRect finalFrame = transitionContext.containerView.bounds;
finalFrame.origin.y = finalFrame.size.height - 100; //100 from the bottom of the parent.
finalFrame.size.height = 100; //Present with a size of 100 height.
//Add the controller's view as a subview of the context.
[transitionContext.containerView addSubview:toViewController.view];
[toViewController.view setFrame:startingFrame];
//Start animating from "startFrame" --> "endFrame" with 0.5 seconds duration and no delay. I chose easeIn style.
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
[toViewController.view setFrame:finalFrame];
} completion:^(BOOL finished) {
//We are finished animating, complete the transition!
[transitionContext completeTransition:YES];
}];
}
else {
//If we are dismissing the view controller, we need to animate it down the screen and then remove its view from the context.
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//We only need one frame. This is the first frame. We are animating from "endFrame" --> "startingFrame" (backwards/reverse animation).
CGRect startingFrame = transitionContext.containerView.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
//Start the animation with 0.5 seconds duration and I chose easeOut style.
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[fromViewController.view setFrame:startingFrame];
} completion:^(BOOL finished) {
//Remove the view controller's view from the context and complete the transition!
[fromViewController.view removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
}
#end
Example (Without Transition Animation):
//
// ViewController.m
// SO2
//
// Created by Brandon Thomas on 2017-01-23.
// Copyright © 2017 XIO. All rights reserved.
//
#import "ViewController.h"
#interface ModalController : UIViewController
#end
#implementation ModalController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
#end
#interface ViewController ()
#property (nonatomic, assign) bool presentingModalController;
#property (nonnull, nonatomic, strong) ModalController *modalController;
#property (nonnull, nonatomic, strong) UIButton *button;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.button = [[UIButton alloc] initWithFrame:CGRectMake(15, self.view.center.y - 100, self.view.frame.size.width - 30, 45)];
[self.button setTitle:#"Present" forState:UIControlStateNormal];
[self.button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.button setBackgroundColor:[UIColor lightGrayColor]];
[self.button.layer setCornerRadius:5.0];
[self.button addTarget:self action:#selector(onButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.button];
self.modalController = [[ModalController alloc] init];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)onButtonClicked:(UIButton *)button {
if (self.modalController.view.window == nil) {
//Present
CGRect startingFrame = self.view.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
CGRect finalFrame = self.view.bounds;
finalFrame.origin.y = finalFrame.size.height - 100; //100 from the bottom of the parent.
finalFrame.size.height = 100; //Present with a size of 100 height.
[self.modalController.view setFrame:startingFrame];
[self.modalController willMoveToParentViewController:self];
[self addChildViewController:self.modalController];
[self.view addSubview:self.modalController.view];
[self.modalController didMoveToParentViewController:self];
[UIView animateWithDuration:0.5 animations:^{
[self.modalController.view setFrame:finalFrame];
} completion:^(BOOL finished) {
}];
}
else {
//Dismiss
CGRect startingFrame = self.view.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
[UIView animateWithDuration:0.5 animations:^{
[self.modalController.view setFrame:startingFrame];
} completion:^(BOOL finished) {
[self.modalController.view removeFromSuperview];
[self.modalController willMoveToParentViewController:nil];
[self.modalController removeFromParentViewController];
[self.modalController didMoveToParentViewController:nil];
}];
}
}
#end
Check out the Apple documentation for this:
Presenting a View Controller Using Custom Animations
To present a view controller using custom animations, do the following
in an action method of your existing view controllers:
Create the view controller that you want to present. Create your
custom transitioning delegate object and assign it to the view
controller’s transitioningDelegate property. The methods of your
transitioning delegate should create and return your custom animator
objects when asked. Call the
presentViewController:animated:completion: method to present the view
controller. When you call the
presentViewController:animated:completion: method, UIKit initiates the
presentation process. Presentations start during the next run loop
iteration and continue until your custom animator calls the
completeTransition: method. Interactive transitions allow you to
process touch events while the transition is ongoing, but
noninteractive transitions run for the duration specified by the
animator object.
EDIT:
Your alternative option is to create a container with your custom sizes and animate your UIViewController added as a child view of your UIViewController:
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
Taken from this Thread
I've read multiple solutions here on SO, but they all don;t seem to work in my case.
I've got a class NotificationBar:
.h file:
#interface NotificationBar : UIView <UIGestureRecognizerDelegate>
#property (strong, nonatomic) UILabel *messageLabel;
- (id)initWithFrame:(CGRect)frame message:(NSString *)message;
- (void) show:(int)duration;
#end
.m file:
#import "NotificationBar.h"
#implementation NotificationBar
#synthesize messageLabel;
- (id)initWithFrame:(CGRect)frame message:(NSString *)message
{
self = [super initWithFrame:frame];
if (self)
{
self.alpha = 0;
self.backgroundColor = [UIColor cabmanBlue];
self.userInteractionEnabled = YES;
self.messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.bounds.origin.x + 5, 0 , self.bounds.size.width - 10, self.bounds.size.height)];
[self.messageLabel setBackgroundColor:[UIColor clearColor]];
[self.messageLabel setTextColor:[UIColor whiteColor]];
[self.messageLabel setFont:[UIFont boldSystemFontOfSize:14]];
[self.messageLabel setNumberOfLines:2];
self.messageLabel.text = message;
[self addSubview:messageLabel];
UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismiss:)];
tapRec.delegate = self;
tapRec.cancelsTouchesInView = NO;
[self addGestureRecognizer:tapRec];
}
return self;
}
- (void) show:(int)duration
{
[[[[[UIApplication sharedApplication] keyWindow] subviews] lastObject] addSubview:self];
[UIView animateWithDuration:0.5 animations:^{ self.alpha = 1; } completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 delay:duration options:UIViewAnimationOptionCurveLinear animations:^{ self.alpha = 0;} completion:^(BOOL finished)
{
if(finished) [self removeFromSuperview];
}];
}];
}
- (void) dismiss:(UITapGestureRecognizer *)gesture
{
[self.layer removeAllAnimations];
[UIView animateWithDuration:0.5 animations:^{ self.alpha = 0; } completion:^(BOOL finished) {
if(finished)
{
[self removeFromSuperview];
}
}];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
#end
And I use the class in the following way:
- (void) displayNotificationMessage:(NSString *)message withSound:(BOOL) withSound
{
UIView *topView = [self.window.subviews lastObject];
[[[NotificationBar alloc] initWithFrame:CGRectMake(topView.bounds.origin.x,
64,
topView.bounds.size.width,
40)
message:message] show:10];
if(withSound) AudioServicesPlaySystemSound(1007);
}
The view is always presented and shown. The dismiss function isn't triggered and it seems it doesn't respond to anything. displayNotificationMessage is placed in AppDelegate. displayNotificationMessage is some times used when a viewcontroller with a mapview is displayed or in a UITableViewController. You could say it has to work the same way as UIAlertView: always presented no matter in which screen the user is.
Does anyone see an error or something?
Thank in advance!
Your UILabel takes over almost your entire view bounds:
self.messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.bounds.origin.x + 5, 0 , self.bounds.size.width - 10, self.bounds.size.height)];
You may try reducing it's size and then see if the gesture works in the other locations or you can try adding the gesture to the label
[self.messageLabel addGestureRecognizer:singleTap];
I just tried your code on a custom UIView and it works just fine. Having the UILabel as a subview has no effect, it responds well. To me it looks like you probably have either another UIView put over this one (which makes this one buried and therefore unresponsive) or you have another Tap Gesture registered in your superview.
I have a method that drops down a UIView. That works fine. How would I go about having it slide back up when the button is touched a second time?
I also need to deactivate it somehow so that it doesn't repeat the animation if it's already displayed.
- (void)slideDownTableView {
self.tableViewCities = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableViewCities.frame = CGRectMake(0,0,300,180);
self.tableViewCities.autoresizingMask = ( UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth );
self.tableViewCities.dataSource = self;
self.tableViewCities.delegate = self;
self.tableViewCities.backgroundColor = [UIColor whiteColor];
UIView *myview=[[UIView alloc] initWithFrame: CGRectMake(10, 0,300,180)];
myview.backgroundColor=[UIColor redColor];
[myview addSubview:self.tableViewCities];
[self.view addSubview:myview];
myview.frame = CGRectMake(10, 0,300,-180); // offscreen
[UIView animateWithDuration:0.5
animations:^{
myview.frame = CGRectMake(10, 0,300,180); // final location move
}];
}
iOS provides autoreverse property by which animation reverses automatically.
UIViewAnimationOptionAutoreverse will give you an effect as the animation reverses .
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
// do whatever animation you want, e.g.,
someView.frame = someFrame1;
}
completion:NULL];
Using BeginFromCurrentState option worked for me.
if(!enabled){
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationTransitionCurlUp
animations:^{
myview.frame = CGRectMake(10, 0,300,180);
}
completion:nil];
enabled=YES;
}
else{
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationTransitionCurlUp
animations:^{
myview.frame = CGRectMake(10, 0,300,-180);
}
completion:nil];
enabled=NO;
}
With a simple BOOL to store the current state of the sliding UIView you can achieve this.
The initial state is not visible if I understand well? So initially the BOOL citiesVisible is set to N0, then the button's action check its value, and performs a different animation accordingly. Simple.
Finally, I also disable the button during the animation to prevent the user to trigger a second animation while a first one is already performing.
I rewrote a bit your code to follow some best practices, and improve readability/maintainability:
Setting up the views beforehand because the button's action responsibility shouldn't be to setup the views, but only to animate them.
Changed the signature of the target action, that's my personal style guide to state in the sender's name and the ControlEvent, it makes it more tidy when you have multiple buttons/actions ;). Also per Apple guidelines, methods receiving actions should have one "id" parameter, the sender.
Store the frames in variables (you could also have them as #define, that'd be even better I think), so you don't have to hard-code them twice, it reduces the risk of breaking the animation by having a view sliding to different positions...
Code update:
#interface ViewController ()
#property (nonatomic, strong) UIButton *showCitiesButton;
#property (nonatomic, strong) CGRect citiesInitialFrame;
#property (nonatomic, strong) CGRect citiesVisibleFrame;
#property (nonatomic, strong) UITableView *tableViewCities;
#property (nonatomic, strong) UIView *citiesContainer;
#property (nonatomic, strong) BOOL citiesVisible;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Setting up the cities view
self.citiesInitialFrame = CGRectMake(10, -180, 300, 180);
self.citiesVisibleFrame = CGRectMake(10, 0, 300, 180);
self.tableViewCities = [[UITableView alloc] init ...];
self.citiesContainer = [[UIView alloc] init ...];
// ...
self.citiesVisible = NO;
[self.showCitiesButton addTarget:self
action:#selector(showCitiesButtonDidTouchUpInside:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)showCitiesButtonDidTouchUpInside:(id)sender
{
[self.showCitiesButton setEnabled:NO];
if (self.citiesVisible)
{
// Cities view is visible, sliding it out of the screen
[UIView animateWithDuration:0.5
animations:^{
self.citiesContainer.frame = self.citiesInitialFrame;
} completion:^(BOOL finished) {
self.citiesVisible = NO;
[self.showCitiesButton setEnabled:YES];
}];
}
else
{
// Cities view is not visible, sliding it in
[UIView animateWithDuration:0.5
animations:^{
self.citiesContainer.frame = self.citiesVisibleFrame;
} completion:^(BOOL finished) {
self.citiesVisible = YES;
[self.showCitiesButton setEnabled:YES];
}];
}
}
#end
You can reverse UIView animation with this code:
if (isMarked) {
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform=CGAffineTransformMakeScale(1.1, 1.1);
}
completion:nil];
} else {
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform=CGAffineTransformIdentity;
}
completion:nil];
}
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.
I have a requirement to show a status bar at certain times at the bottom of my application. I can easily put this at the bottom of my application's main view, but whenever I push a view controller on top of this (either modally or not) it hides this status bar.
Is there any way I can add a status bar like this, and have it be outside the bounds of my application itself? Ideally I'd like this to work like the call-in-progress status bar on the iPhone - when this bar appears, the app is pushed down, and a call to [[UIScreen mainScreen] applicationFrame] returns the correct size (i.e. it accounts for the presence of this status bar when calculating the height available for the app).
I wanted to do this, too, so I tried View Controller Containment. I'm still trying it out, so I'm not willing to give this a ringing endorsement, but it might be something you'd want to try playing around with yourself if you're in iOS5. But it appears to give you a status bar that will appear or disappear from the bottom of the screen.
This is a view controller that will open another view controller, but if there is status text to show, it pops up from the bottom of the screen and stays there until you get rid of it. I've only done a little testing so far, but it looks like this handles pushViewController/popViewController, but maybe not modal views.
My header looks like:
// StatusBarViewController.h
//
// Created by Robert Ryan on 7/8/12.
#import <UIKit/UIKit.h>
#interface StatusBarViewController : UIViewController
#property (strong, nonatomic) UIViewController *appController;
- (void)setStatus:(NSString *)text;
#end
My implementation file (this is ARC) looks like:
// StatusBarViewController.m
//
// Created by Robert Ryan on 7/8/12.
#import "StatusBarViewController.h"
#interface StatusBarViewController ()
{
BOOL _statusHidden;
UIView *_appView;
UILabel *_statusLabel;
}
#end
#implementation StatusBarViewController
#synthesize appController = _appController;
- (void)dealloc
{
_appView = nil;
_statusLabel = nil;
[self setAppController:nil]; // usually I don't like setters in dealloc, but this does some special stuff
}
- (void)createControlsWithStatusHidden
{
// create default app view that takes up whole screen
CGRect frame = self.view.frame;
frame.origin = CGPointMake(0.0, 0.0);
_appView = [[UIView alloc] initWithFrame:frame];
_appView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_appView.clipsToBounds = YES;
[self.view addSubview:_appView];
// create status label that is just off screen below the app view
_statusLabel = [[UILabel alloc] init];
_statusLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size:12.0];
_statusLabel.backgroundColor = [UIColor darkGrayColor];
_statusLabel.textColor = [UIColor whiteColor];
CGSize size = [#"Hey!" sizeWithFont:_statusLabel.font]; // test size of box with random text
_statusLabel.frame = CGRectMake(0.0, frame.size.height, frame.size.width, size.height);
_statusLabel.textAlignment = UITextAlignmentCenter;
_statusLabel.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:_statusLabel];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self createControlsWithStatusHidden];
_statusHidden = YES;
// I'm instantiating from storyboard. If you're using NIBs, just create your controller controller using initWithNib and then set our appController accordingly.
self.appController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainNavigator"];
}
- (void)setAppController:(UIViewController *)controller
{
if (controller)
{
controller.view.frame = CGRectMake(0.0, 0.0, _appView.frame.size.width, _appView.frame.size.height);
[self addChildViewController:controller];
[controller didMoveToParentViewController:self];
if (self.appController)
{
// if we have both a new controller and and old one, then let's transition, cleaning up the old one upon completion
[self transitionFromViewController:self.appController
toViewController:controller
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseInOut
animations:nil
completion:^(BOOL finished){
if (self.appController)
{
[self.appController willMoveToParentViewController:nil];
[self.appController removeFromParentViewController];
}
}];
}
else
{
// if we have no previous controller (i.e. this is our first rodeo), then just add it to the view
[_appView addSubview:controller.view];
}
}
else
{
// no new controller, so we're just removing any old on if it was there
if (self.appController)
{
// if there was an old controller, remove it's view, and remove it from the view controller hierarchy
[self.appController.view removeFromSuperview];
[self.appController willMoveToParentViewController:nil];
[self.appController removeFromParentViewController];
}
}
_appController = controller;
}
- (void)hideStatusWithCompletion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:0.25
animations:^{
CGRect labelFrame = _statusLabel.frame;
labelFrame.origin.y += labelFrame.size.height;
_statusLabel.frame = labelFrame;
CGRect appFrame = _appView.frame;
appFrame.size.height += labelFrame.size.height;
_appView.frame = appFrame;
}
completion:completion];
}
- (void)unhideStatusWithCompletion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:0.25
animations:^{
CGRect labelFrame = _statusLabel.frame;
labelFrame.origin.y -= labelFrame.size.height;
_statusLabel.frame = labelFrame;
CGRect appFrame = _appView.frame;
appFrame.size.height -= labelFrame.size.height;
_appView.frame = appFrame;
}
completion:completion];
}
- (void)setStatus:(NSString *)text
{
BOOL hasText = (text && [text length] > 0);
if (hasText)
{
if (!_statusHidden)
{
// if we have text, but status is already shown, then hide it and unhide it with new value
[self hideStatusWithCompletion:^(BOOL finished){
_statusLabel.text = text;
[self unhideStatusWithCompletion:nil];
}];
}
else
{
// if we have text, but no status is currently shown, then just unhide it
_statusLabel.text = text;
[self unhideStatusWithCompletion:nil];
}
_statusHidden = NO;
}
else
{
if (!_statusHidden)
{
// if we don't have text, but status bar is shown, then just hide it
[self hideStatusWithCompletion:^(BOOL finished){
_statusLabel.text = text;
}];
_statusHidden = YES;
}
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
And then, any view controller that wants to update the status message would use a method kind of like:
- (void)setStatus:(NSString *)text
{
UIViewController *controller = [UIApplication sharedApplication].delegate.window.rootViewController;
if ([controller isKindOfClass:[StatusBarViewController class]])
{
[(StatusBarViewController *)controller setStatus:text];
}
}