In iOS 6 I'm used to present keyboard in viewDidLoad.
- (void)viewDidLoad
{
[super viewDidLoad];
[txtField becomeFirstResponder];
}
This way, when navigationController pushes the new viewController, keyboard is already there, animating smoothly from left to right and avoiding bottom-up animation.
In iOS 7 this behavior seems broken.
If I add [txtField becomeFirstResponder] in viewDidLoad, keyboard appears in the middle of pushing animation, already in its final position: an unpleasant effect!!
I've tried to move [txtField becomeFirstResponder] in viewWillAppear, but the final result is unchanged.
Do you know a way to get back iOS 6 behavior, pushing the new viewController and the keyboard all together?
EDIT: Using a timer doesn't work either... whatever time delay I set, the keyboard is shown only at the end of pushing animation.
So far, my best try it is to put [txtField becomeFirstResponder] in viewWillLayoutSubviews or viewDidLayoutSubviews. Unfortunately, doing so working when pushing viewController but not when popping back (the keyboard doesn't appear).
I've managed to extrapolate your workaround in viewWillLayoutSubviews to force it to work.
- (void)viewWillLayoutSubviews {
if (![self.textField1 isFirstResponder] && ![self.textField2 isFirstResponder] && ...) {
[self.textField1 becomeFirstResponder];
}
}
This is working for me for both pushing onto the stack, and after dismissing a modal view controller.
Related
I have a pan gesture that I have added on my app's root view controller on the 1st level to let the user swipe anywhere to go to the previous level in the navigation stack (the gesture is not on the 0 level to prevent going into a black hole :) ). This has worked and still works perfectly fine on iOS versions prior to 7.
When I try to swipe back using iOS 7, I get these messages in the console:
[11050:a0b] nested pop animation can result in corrupted navigation bar
[11050:a0b] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
At this point, the app hasn't crashed.
Then when I try to swipe back one more time to the previous level, it crashes. I'm just trying to figure out why this bug has risen in iOS 7, and how I can fix it. Any help is duly and greatly appreciated!
Here is my code:
RootViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(pop:)];
pan.delegate = self;
[self.view addGestureRecognizer:pan];
}
- (void)pop:(UIPanGestureRecognizer*)pan
{
if (pan.state == UIGestureRecognizerStateBegan || pan.state == UIGestureRecognizerStateChanged)
{
CGPoint vel = [pan velocityInView:self.view];
if (vel.x > 1000)
{
[self.navigationController popViewControllerAnimated:YES];
}
}
}
I suspect the problem is your state condition. By testing for UIGestureRecognizerStateBegan and UIGestureRecognizerStateChanged, the delegate is triggering many pops (which are animated, so the next pop is triggered while the previous one is still animating).
Instead, test if (pan.state == UIGestureRecognizerStateEnded) to get just the one you need.
Also, don't worry about popping into nowhere when the NavVC is already at root. It's smart enough not to pop the root.
(As to why this is misbehaving in os7 only, I can only speculate that < ios7 logic removes the gesture recognizers at an earlier stage in the pop process).
If there is a back button visible for the current controller in the UINavigationController's stack, the user will be able to swipe right from the left edge of the screen to go back. You may want to consider not adding the pan gesture recognizer if the app is running on iOS7.
You can check the iOS version using [UIDevice currentDevice].systemVersion.
I realize your question stated that you added the gesture recognizer to allow the user to swipe anywhere to go back, but (hopefully) iOS7 users will quickly become familiar with the swipe from the left edge of the screen.
The thing happening when the velocity reaches 1000 its gonna pop the viewcontroller everytime the function is called and when its animated is YES it will get called several times before it pops the viewcontroller which I think gives you the crash.
I have a very weird bug here, this is the scenario;
-I click on one of the textfields on my UITableView, uikeyboard appears,
-Then without pressing done, I click another textfield on screen(a disabled one),
-This disabled textfield pushes a detailview controller,
-I do my job on the detailview and return back,
-Surprize! the main UITableview is no more scrollable to bottom anymore! so I can not edit the bottom cells anymore!
This does not happen IF I press done after editing(so close the keyboard) and then click the detail view, now it works good.
I think it is something about resignfirstResponder is not called before I switch to another view, so I tried to send this msg to all the textfields in the tableview..but it got worse.
I tried also adding this line below just before I push the detailviewcontroller, but not worked, this is interssting cause this is what I call to resign the number pad normally, and it works when it is called from my custom "done" button to dismiss the nuber pad
[[self view] endEditing:YES];
And this is how I set the uikeyboards depend on the content in cellforRowAtIndex method
if(somelogic){
cell.textField.keyboardType=UIKeyboardTypeNumberPad;
[self resignFirstResponder];
[self becomeFirstResponder];
}
else{
cell.textField.keyboardType=UIKeyboardTypeDefault;
[self resignFirstResponder];
[self becomeFirstResponder];
}
Any Ideas?
Just call [currentTextField resignFirstResponder] before you push the detail view controller.
Set the currentTextField in the following method.
- (void)textFieldDidBeginEditing:(UITextField *)textField {
currentTextField = textField;
// Your code here
}
Occam's razor.
Don't let them not click the done button.
Put a transparent UIButton accross the entire screen. Show it when you call [textField becomeFirstResponder]. You can either prevent people from touching the screen and switching textFields, or call [textField resignFirstResponder] when it is tapped.
I have the following code
- (void) viewWillAppear:(BOOL)animated
{
NSLog(#"dismiss view");
[self dismissModalViewControllerAnimated:YES];
}
This prints dismiss view but won't execute the view dismissal code.
Can a view be dismissed in code without a trigger from an IBAction?
Why wouldn't this be executing?
You would probably have better luck putting the code into viewDidAppear rather than viewWillAppear. The latter tends to be called right at the start of any animation such as a modal beginning to slide off the screen; the former tends to be called when that animation has completely finished. Note that even if this strategy works, you may end up with a weird effect whereby two modals are seen to slide off, one after the other; I presume you just want one sliding off effect.
What happens if you dismiss the "lower down" modal dialog (the one first pushed), and don't bother dismissing the one that is topmost?
Also consider paying attention to the animated argument when dismissing your modal view controller. Different combinations of animated or not can have different effects when you have problems like yours.
If you post a more complete code sample we can give a better answer!
try with a little delay on the second dismiss.
- (void) viewWillAppear:(BOOL)animated
{
NSLog(#"dismiss view");
[self performSelector:#selector(delayedDismiss) withObject:nil afterDelay:0.5];
}
-(void)delayedDismiss{
[self dismissModalViewControllerAnimated:YES];
}
I wrote simple code to test UIImagePickerController:
#implementation ProfileEditViewController
- (void)viewDidLoad {
[super viewDidLoad];
photoTaker_ = [[UIImagePickerController alloc] init];
photoTaker_.delegate = self;
photoTaker_.sourceType = UIImagePickerControllerSourceTypeCamera;
photoTaker_.showsCameraControls = NO;
}
- (void)viewDidAppear: (BOOL)animated {
[self presentModalViewController: photoTaker_ animated: NO];
}
#end
And I'm getting strange warnings like the following:
2010-05-20 17:53:13.838 TestProj[2814:307] Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
2010-05-20 17:53:13.849 TestProj[2814:307] Using two-stage rotation animation is not supported when rotating more than one view controller or view controllers not the window delegate
Got any idea what this is about? Thanks a lot in advance!
This message will appear if you are presenting the UIImagePickerController within another UIViewController. Because it isn't pushed like a UINavigationController stack, there is confusion at the UIWindow level. I don't know if the warning is a problem, but to eliminate the warning you can do the following:
// self = a UIViewController
//
- (void) showCamera
{
cameraView = [[UIImagePickerController alloc] init];
[[[UIApplication sharedApplication] keyWindow] setRootViewController:cameraView];
[self presentModalViewController:cameraView animated:NO];
}
- (void) removeCamera
{
[[[UIApplication sharedApplication] keyWindow] setRootViewController:self];
[self dismissModalViewControllerAnimated:NO];
[cameraView release];
}
Perhaps you are adding the root UIViewController's view as a subview of the window instead of assigning the view controller to the window's rootController property?
IT ALL FALLS BACK ON THE UI
This warning can be implemented for several different objects: Pickers, keyboard, etc.
I have found that it is related to the UI taking two steps to complete a transition or other animation. Or for any instance where the UI is trying to finish one thing and is being asked to execute another before it has finished. (therefore it covers a wide range of possible triggers)
I have seen the warning appear on 4.0 & 4.2. In my case I was working with rotating the device and catching whether the keyboard was still up-(i.e. text field was still first responder). If so, the keyboard needed to stay up for between the views, but this presented other complications with other views.
Therefore, I implemented a BOOL tracker to keep track if keybaordIsPresent, and if so I was {textfield resignFirstResponder]; when detecting the orientation change and the reset the textfield to becomeFristResponder after the transition that was wrapped in an Animation Block. My BOOL tracker worked better, I still use the NSNotifications for the Keyboard, but there were overlaps of notifications during rotations because the keyboard was being dismissed without requesting such. The BOOL is set to NO on Load, and when the [textfield resignFirstResponder]; is implemented. *not when "-(void)keyboardWillhide is trigger by the NSNotifications, which gives me both working triggers that never conflict. The BOOL is set to YES, only when the user touches textfield which automatically triggers becomeFirstResponder.
I removed the warning by taking the [textfild resignFirstResponder]; out of the
-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}
}
and placing it back at the top of the code for the:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if (keyboardIsPresent == YES) {
[self.entryTextField resignFirstResponder];
}
//Determine Which Orientation is Present:
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait) || (fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)){
//LANDSCAPE VIEW:
[self configureLandscapeView];
}else {
//PORTRAIT VIEW:
[self configurePortraitView];
}
}
**Even though I had no code inside the -(void)willAnimatFirstHalfOfRotationToInterface:, the warning was still popping up. I think the warning was still popping up because the compiler still has to attempt the method while it is trying to execute the first animation and therefore gets the double animation call or overlap of resources. It doesn't know that there is no executable code with the method until after it runs through it. And by that time it already set aside resource in preparation for handling possible actions within the method.
**To ellimiate the warning I had to remove or nil out the code for the willAnimateFirstHalfOfRotation, so that the compiler does not have to even check to see if there is a possible 2nd animation or action that may need to be executed at the same time.
/*-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}}*/
After the transition is completed, within the original animation block I check to see if the "keyboardIsPresent" was YES prior to the rotation, and if so, I resign the First Responder once again. I use setAnimationDuration:0.3which comes out pretty clean and not jumpy.
Well, you are presenting UIImagePickerController modally inside viewDidAppear of ProfileEditViewController.
Think about this. That means when ProfileEditViewController view appears, the UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and.... you get the idea.
That warning is rather cryptic though, not sure if that is what it's trying to tell you. I would suggest making a button somewhere on the ProfileEditViewController that calls presentModalViewController and also make sure you have a way to dismiss the UIImagePickerController (I've never used it not sure if it has one automatically).
You may be trying to present two modal view controllers at the same time, and they are fighting for animation resources.
1) There is rarely any UI reason to do this. You could instead just go directly to the second view controller (the image picker); and, after dismissing it, then present the first view or view controller.
2) If you do want two stacked view controllers or a view controller on top of a view, then set a timer in viewDidAppear to present the second view controller after the first one has finished it's animation. (You could display a dummy png image of a blank picker in the first one to prevent too much display flashing until the second view controller goes live.)
EDIT - Added a random code example:
I might try substituting this as an experiment:
- (void)foo {
[self presentModalViewController: photoTaker_ animated: NO];
}
- (void)viewDidAppear: (BOOL)animated {
NSTimer *bar = [ NSTimer scheduledTimerWithTimeInterval: (2.0f)
target: self
selector: #selector(foo)
userInfo: nil
repeats:NO ];
}
A shorter time delay may work as well.
I just had the same problem. In my case was a silly mistake that I'm putting here just in case anyone else falls into that same issue.
In my tabbed app I remove one of the original ViewControllers and added a new one with Storyboard to create a "Settings" section.
This new VC had to be a table view VC and even I designed, compiled and run it without a problem, when I changed the orientation of the app I kept getting this “Using two-stage rotation animation” error.
My problem was that I forgot to change in the original .h file interface "UIViewController" for "UITableViewController".
Once this was done I changed on the Storyboard identity badge the class from the general value to my SettingsViewController and that was the end of it.
I hope it can help someone else. It took me a while to get to bottom of this.
Cheers,
I think the warning here is about Core Animation performance. As a test, I loaded the image picker without any action sheet or other animations going on and the warnings are still there. I think these are warnings coming from the image picker class itself and not from any misuse of the API.
I'm trying to detect a shake on the iPhone device, so for what i've read I need to set my view controller as first responder on the viewDidAppear method. This method is supposed to be called automatically, but for some reason this method is never called. Only viewDidLoad is called.
this is my code (I put only the relevant parts):
BuildHouseViewController.h:
#interface BuildHouseViewController : UIViewController {
BuildHouseViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view becomeFirstResponder];}
-(void)viewDidAppear:(BOOL)animated{
[self becomeFirstResponder];
[super viewDidAppear:animated];}
-(BOOL)canBecomeFirstResponder {
return YES;}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
NSLog(#"shake");
if ( event.subtype == UIEventSubtypeMotionShake )
{ }
if ( [super respondsToSelector:#selector(motionEnded:withEvent:)] )
[super motionEnded:motion withEvent:event];
}
I added breakpoints on the viewDidAppear method and it is never called. Shakes are never detected, I suppose it is because as this methods are never called, so the view controller can never become first responder. I don't understand why this is happening.
Any help will be appreciated.
Edit:
I call the view from another view using:
[self.view addSubview:nextScreen.view];
The view is displayed on screen
Thanks for the quick answers.
I've found something interesting. I tried loading the same view I'm having problems with in different ways and I'm getting different results
-As I said before if I call it from another view using:
[self.view addSubview:nextScreen.view];
viewDidLoad is never called and I cannot detect shakes.
-Now if I call it from the AppDelegate using:
[window addSubview:nextScreen.view];
viewDidLoad is called!! and I am able to detect shakes, however this solution is not possible, I should be able to call it from another view
-If I call it from another view using:
[self presentModalViewController:nextScreen animated:YES];
viewDidLoad is called!! However I don't want to use a modal view controller, but it appears to be the only solution to my problem, shakes are detected.
It is strange that the first method doesn't load the view correctly, is it a bug??
The [self becomeFirstResponder] and the like don't actually make that become the first responder. The method gets called when the view is going to become the first responder. So that's not doing what you think it is.
Secondly, the viewDidAppear will only be called when the view, well, did appear. Is it showing up on the screen? Where are you telling it to be displayed? You need to either add the view controller's view as a subview of another view, or pushed onto a navigation controller stack, or pushed as a modal view.
viewDidAppear:(BOOL)animated only gets called when the view is shown by UINavigationController or UITabBarController. If you add a view controller's view to a subview (such as a scrollview or what have you), it won't get called. You would think it would but you'd be wrong. Just got bit by this myself.
Further to #Genericrich's comments, you can manually call viewDidAppear after you put the subview in yourself.
[self.view addSubview:theViewController.view];
[theViewController viewDidAppear:FALSE];
This worked for me. Hope it helps someone else.