I have a barButtonItem that calls an action which swaps two views in an animation block. I disable the barButtonItem at the beginning of the action method that it calls, and the I want to enable it when its finished, so that the user can't flip the view again before it has finished flipping.
It does this in a UIView animation block, so If I enable it at the ned of the method, it will be instant and that destroys the purpose because it gets enabled before the animation finishes.
In the method, I cast sender to a UIBarButtonItem, and I disable that. So I found [UIView setAnimationDidStopSelector:#selector()], but I need to pass my barButtonItem in the selector so the enableControl Method will enable that (this would be a lot easier if I could make an anonymous function..).
So how can I pass the barButton as an argument? I'd rather not have to make an ivar just for this.. Here's the code:
- (void)barBtnItemSwitchViews_Clicked:(id)sender {
UIBarButtonItem *barButton = (UIBarButtonItem *)sender;
barButton.enabled = NO;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.8];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
if(!self.yellowVC.view.superview) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:innerView cache:YES];
[yellowVC viewWillAppear:YES];
[blueVC viewWillDisappear:YES];
[blueVC.view removeFromSuperview];
self.blueVC = nil;
[innerView addSubview:yellowVC.view];
} else if(!self.blueVC.view.superview) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:innerView cache:YES];
[blueVC viewWillAppear:YES];
[yellowVC viewWillDisappear:YES];
[yellowVC.view removeFromSuperview];
self.yellowVC = nil;
[innerView addSubview:blueVC.view];
}
[UIView commitAnimations];
[UIView setAnimationDidStopSelector:#selector(enableControl:)]; // How do I pass barButton??
}
- (void)enableControl:(UIControl *)control {
control.enabled = YES;
}
Here's what I changed it to, but the method isn't being called.. What am I doing wrong?
- (void)barBtnItemSwitchViews_Clicked:(id)sender {
UIBarButtonItem *barButton = (UIBarButtonItem *)sender;
barButton.enabled = NO;
[UIView beginAnimations:nil context:barButton];
[UIView setAnimationDuration:0.8];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
if(!self.yellowVC.view.superview) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:innerView cache:YES];
[yellowVC viewWillAppear:YES];
[blueVC viewWillDisappear:YES];
[blueVC.view removeFromSuperview];
self.blueVC = nil;
[innerView addSubview:yellowVC.view];
} else if(!self.blueVC.view.superview) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:innerView cache:YES];
[blueVC viewWillAppear:YES];
[yellowVC viewWillDisappear:YES];
[yellowVC.view removeFromSuperview];
self.yellowVC = nil;
[innerView addSubview:blueVC.view];
}
[UIView setAnimationDidStopSelector:#selector(flipViewAnimationDidStop:finished:context:)];
[UIView commitAnimations];
}
- (void)flipViewAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
UIBarButtonItem *barButton = (UIBarButtonItem *)context;
barButton.enabled = NO;
NSLog(#"Disabled");
}
EDIT: I figured it out. I had to make the button the context, and then do setAnimationDelegate:self, and then setAnimationDidEndSelector:#selector(myMethod:finished:context:), and then in the method cast context to uibarbuttonitem and re-enable it.
From UIView's setAnimationDidStopSelector: docs:
The selector should be of the form: - (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context.
The third argument, context, is an object of your choice, set in beginAnimations:context:. So it seems like what you're missing is to use barButton as the context argument, and then rewrite enableControl with a proper signature. In that method, you take the third argument, context, and cast it from void* to a UIBarButtonItem*.
I dont think you can pass more than one argument to the selector by using #selector, however you could make your button a class variable and enable or disable it like that in the selector (instead of it being passed as an argument), to define a selector with multiple arguments you can use NSInvocation, like so
NSInvocation *inv = [[NSInvocation alloc] init];
[inv setSelector:#selector(foo:bar:)];
[inv setArgument:123 atIndex:0];
[inv setArgument:456 atIndex:1];
But I dont think this will work for what you are trying to do.
Related
I have code:
ListViewController * listViewController = [[ListViewController alloc] initWithNibName:#"ListViewController" bundle:nil];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
[self viewWillDisappear:YES];
[listViewController viewWillAppear:YES];
self.view.hidden = YES;
listViewController.view.hidden = NO;
[self viewDidDisappear:YES];
[listViewController viewDidAppear:YES];
[UIView commitAnimations];
But it does not works, and listViewController does not displayed( Please, somebody can tell me the solution of this problem?
Try something like:
UIViewAnimationOptions ops = UIViewAnimationOptionTransitionFlipFromRight;
NSArray *temp = [[NSBundle mainBundle] loadNibNamed:#"NameOfNib" owner:self options:nil];
UIView* newView = [[temp objectAtIndex:0] view];
[UIView transitionFromView:self.view toView:newView duration:1.5 options:ops completion:nil];
self.view = newView; //Lets you control the new view from the current controller (you might want to save a reference to the old one if you need to change back)
As meronix said, non-block based animation is discouraged by Apple for the newer iOS versions. The above method is the "approved" way to do it.
Just so you know, the viewWillAppear, viewDidDisappear, and similar methods aren't methods that YOU call to make the view do things. They're called automatically when these things happen.
Your code had a few misunderstandings; I've commented on them below
//This looks fine (depending on what is in the nib)
ListViewController * listViewController = [[ListViewController alloc] initWithNibName:#"ListViewController" bundle:nil];
//Normally I use these to move things around, not change the view
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
[self viewWillDisappear:YES]; //These two methods aren't things that you call
[listViewController viewWillAppear:YES];
self.view.hidden = YES; //If you're flipping or otherwise moving a view out of
listViewController.view.hidden = NO; //sight then you don't need to hide/unhide views
[self viewDidDisappear:YES]; //Same as above, you don't call these
[listViewController viewDidAppear:YES];
[UIView commitAnimations];
Remove unnecessary code and just write this ...
ListViewController * listViewController = [[ListViewController alloc] initWithNibName:#"ListViewController" bundle:nil];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
[self.view addSubview:listViewController.view];
[UIView commitAnimations];
it cannot work!
you just create and allocate a UIViewController, but never push it on any stacks, or add its view to a visible view.
when you set listViewController.view.hidden to no, you are not magically showing it on screen: you need to add its view to a view (or window) which is already on screen...
ps beginAnimation is deprecated: use blocks animation instead...
I am using xcode 4.2 and ARC. Below is the code I am using. When I click on any button my application crashes and it highlights this code in main.m
Sometimes I get this error
-[__NSCFTimer parentLogin:]: unrecognized selector sent to instance
and sometimes application crashes without any error.
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
My code is:
In my ViewController.m I am using this code to get to the ChooseLogin view controller.
- (IBAction)action:(id)sender {
ChooseLogin *loginScreen = [[ChooseLogin alloc] initWithNibName:#"ChooseLogin" bundle:nil];
[UIView beginAnimations:#"flipview" context:nil];
[UIView setAnimationDuration:2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];
[self.view addSubview:loginScreen.view];
[UIView commitAnimations];
}
Then in ChooseLogin.m:
#implementation ChooseLogin
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (IBAction)parentLogin:(id)sender {
NSLog(#":::::::");
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *passwd = [prefs stringForKey:#"password"];
NSLog(#"data saved");
if (passwd == nil) {
CreatePassword *cPassword = [[CreatePassword alloc] initWithNibName:#"CreatePassword" bundle:nil ];
[UIView beginAnimations:#"flipview" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];
[self.view addSubview:cPassword.view];
[UIView commitAnimations];
}else {
UserPassword *uPasswd = [[UserPassword alloc] initWithNibName:#"UserPassword" bundle:nil];
[UIView beginAnimations:#"flipview" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];
[self.view addSubview:uPasswd.view];
[UIView commitAnimations];
}
}
- (IBAction)childLogin:(id)sender {
ChildLogin *loginScreen = [[ChildLogin alloc] initWithNibName:#"ChildLogin" bundle:nil];
[UIView beginAnimations:#"flipview" context:nil];
// [UIView setAnimationDuration:1];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];
[self.view addSubview:loginScreen.view];
[UIView commitAnimations];
}
#end
-[__NSCFTimer parentLogin:]: unrecognized selector sent to instance
This means that a message is being sent to a class that the class doesn't recognize -- you either haven't written this custom selector (function), or somehow a function is being sent to the wrong class and therefore isn't recognized. XCode is usually pretty good about catching these when trying to write code, but it doesn't seem to check for them in XIB files.
The most frequent cause of this type of issue, in my experience, is when you delete or rename a function, and forget to update the XIB file associated with that class -- or you do update it, and it adds the new version of the function, without forgetting the old one.
You should not add view of one view controller to another.
If you still want to do this. You create controller, take it's view amnd then it get released, because you store reference to it nowhere. make ivar loginScreen and ARC will retain ChooseLogin controller for you.
I want to know how to change view on button click in iPhone without navigation controller?
If you're doing this with a UIViewController, you would probably do this like following:
- (IBAction)change {
UIViewController* viewController = [[UIViewController alloc] init];
[self.view addSubView];
// Save the UIViewController somewhere if necessary, otherwise release it
}
Not sure why you don't want to use a UINavigationController though. If it's the navigation bar at the top you don't want to see, there's a property for that so you can hide that bar. Not sure about it's name, probably navigationBarHidden or something like that. You can look that up in the API.
There are many different ways you can do that, and you should possibly provide more information about your app.
A generic way to do it is the following, with animation:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
[vc1 viewWillDisappear:YES];
[vc2 viewWillAppear:YES];
vc1.view.hidden = YES;
vc2.view.hidden = NO;
[vc1 viewDidDisappear:YES];
[vc2 viewDidAppear:YES];
[UIView commitAnimations];
In this case, both views are there, you only hide/show them as you need. Alternatively, you could add/remove the view from their superview:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
[vc1 viewWillDisappear:YES];
[vc2 viewWillAppear:YES];
[vc1 removeFromSuperview];
[masterController.view addSubview:vc2.view;
[vc1 viewDidDisappear:YES];
[vc2 viewDidAppear:YES];
[UIView commitAnimations];
Place this in Button IBAction method
[mCurrentView removeFromSuperview];
[self.view addSubView:mNewView];
Assume you have two view myView1 and myView2 and one button myUIButton in memory
myView1.tag = 1;
myView2.tag = 2;
myUIButton.tag = 1; //Current visible view is myView1
-(void) ButtonCLicked:(id) sender
{
UIButton* myButton = (UIButton*)sender;
if(sender.tag == 1)
{
[myView1 removeFromSuperview];
[self addSubview:myView2]
myButton.tag = myView2.tag;
}
else
{
[myView2 removeFromSuperview];
[self addSubview:myView1]
myButton.tag = myView1.tag;
}
}
You can place two UIView variables in the header file and hide/show one of them or you can make another layer over the existing view like this:
-(IBAction)changeView {
UIView *view = [[UIView alloc] initWithNibName:#"letMeSeeThis" bundle:nil];
[self.view addSubview:view];
}
When my user presses a button, I want the visible view to shrink and disappear, revealing the parent view underneath. Currently I have this, which almost works:
-(void)deleteNoteTransition
{
self.view.userInteractionEnabled=NO;
[UIView beginAnimations:#"deleteNote" context:nil];
[UIView setAnimationDuration:0.6];
self.view.transform=CGAffineTransformMakeScale(0.01,0.01);
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:)];
[UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)anim finished:(BOOL)flag
{
self.view.userInteractionEnabled=YES;
if ([anim isEqualToString:#"deleteNote"]) {
[self.navigationController popViewControllerAnimated:YES];
}
}
There are two problems with this, however:
The view that does the shrinking is not the "whole" view - it does not include the title bar and toolbar.
The view shrinks to reveal a plain white background, and only then is the second method triggered, so the white background is unceremoniously replaced with the parent view.
Is there a way to include the whole screen and smoothly reveal the parent view controller behind, without getting into OpenGL stuff which is far too complicated for my current level of programming?
This should capture your whole view:
#import <QuartzCore/QuartzCore.h>
UIGraphicsBeginImageContext(viewToCapture.bounds.size);
[viewToCapture.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Once your capture your view to an image, use this method to scale and fade the image:
UIImageView *firstView = nil;
-(void)animateOut:(UIImage*)image {
firstView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0, 320, 480)];
firstView.image = image
[window addSubview:firstView];
[window bringSubviewToFront:firstView];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
[UIView setAnimationTransition:UIViewAnimationTransitionNone forView:window cache:YES];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(startupAnimationDone:finished:context:)];
firstView.alpha = 0.0;
// change this for a different scale
firstView.frame = CGRectMake(-60, -85, 440, 635);
[UIView commitAnimations];
}
- (void)startupAnimationDone:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[firstView removeFromSuperview];
[firstView release];
}
You can get the window from the [UIApplication sharedApplication], or just add it as a subview of the view of the current view controller.
Good Moscow evening to everyone!
I'm still unfamiliar with principles of iPhone animation (btw, does anybody know a big and nice tutorial on this?), but in my project I want to do button "highlighted-not highlighted" flicker to notify the user that its label has changed.
This code doesn't do anything (it's just a fragment of flicker animation):
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration: 0.5];
[button setHighlighted: YES];
[UIView commitAnimations];
And this one highlights the button, but does not do it in an animated form:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration: 0.5];
[button setSelected: YES];
[UIView commitAnimations];
Can anyone help me and say:
What's wrong with this code?
What would fix the problem?
----------------------------------- UPDATE ------------------------------
I tried that kind of code, but it doesn't work either:
// ------------------------
// --- animation ----------
// ------------------------
- (void)animateIn
{
[UIView beginAnimations: #"animateIn" context:nil];
[UIView setAnimationDuration: 0.2];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
[control setBackgroundColor:[UIColor blackColor]];
[UIView commitAnimations];
}
- (void)animateOut
{
[UIView beginAnimations: #"animateOut" context:nil];
[UIView setAnimationDuration: 0.2];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
[control setBackgroundColor:[UIColor whiteColor]];
[UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
if([animationID isEqualToString: #"animateIn"])
{
[self animateOut];
return;
}
else if ([animationID isEqualToString: #"animateOut"])
{
cycleCount++;
if(cycleCount < 3)
[self animateIn];
else
cycleCount = 0;
return;
}
}
#end
//isFlickerOn is declared in .h file as BOOL isFlickerOn;
//flickerTimer is declared in .h file as NSTimer *flickerTimer;
//flickerCount is declared in .h file as int flickerCount;
//control is a UILabel, UIButton background color is really hard to notice
//especially the roundedRect UIButton, so I just flickered a UILabel's textColor
-(void)flickerOn {
if (flickerCount < 5)
{
flickerCount++;
if (!isFlickerOn)
{
[control setTextColor:[UIColor yellowColor]];
isFlickerOn = YES;
}
else
{
[control setTextColor:[UIColor blueColor]];
isFlickerOn = NO;
}
}
else
{
[flickerTimer invalidate];
}
}
-(void)flickerAnimation {
flickerCount = 0;
flickerTimer = [NSTimer scheduledTimerWithTimeInterval:0.3f target:self selector:#selector(flickerOn) userInfo:nil repeats:YES];
}
Not all attributes of a UIView are animatable - and I do not believe "highlighted" or "selected" are.
Typically, it's usable for non-boolean values, like "center", "alpha", "frame" and "bounds".
Try tweaking the alpha instead, and you'll see it will work.
You need to create a callback method that executes when the first animation finishes. You use that callback to do create a deselected animation. Keep in mind that there is no gradual "selection state" like there is for transparency. You have to use the
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
[UIView beginAnimations:#"animateIn" context:NULL];
From Apple's docs:
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
Your method must take the following arguments:
animationID
An NSString containing an optional application-supplied identifier. This is the identifier that is passed to the beginAnimations:context: method. This argument can be nil.
finished
An NSNumber object containing a Boolean value. The value is YES if the animation ran to completion before it stopped or NO if it did not.
context
An optional application-supplied context. This is the context data passed to the beginAnimations:context: method. This argument can be nil.
When the animation is finished, the callback animationDidStop is called and passed in the string #"animateIn". You can use that method to check which animation was called and handle that there.