Dismissing Multiple View Controllers in iOS 5 - objective-c

In iOS 4, if you want to dismiss two nested modal view controllers, the following code works:
[[[[self parentViewController] parentViewController] parentViewController] dismissModalViewControllerAnimated:YES];
However in iOS 5 this method no longer works. Does anybody know how to achieve this result in iOS 5?

If you call dismissViewControllerAnimated: on the view controller that presented the first modal, you'll dismiss of both modals at once. So in your 2nd modal you would do the following:
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:NULL];

I have an app that closes nested modal view controllers through NSNotificationCenter. The notification is received by the VC that I want to navigate back to and all the VC's in between are gone.
In the deeper VC...
NSNotification * notification = [NSNotification notificationWithName:#"BACKTOINDEXNOTE" object:nil];
[[NSNotificationCenter defaultCenter] postNotification:notification];
In the VC I would like to go back to
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismiss) name:#"BACKTOINDEXNOTE" object:nil];
// more init code
}
return self;
}
-(void)dismiss
{
[self dismissModalViewControllerAnimated:YES];
}
This works on iOS 5 device with a project deployed for 4.0+
I hope it helps. If you use this, it will scale to support more VC's in between your current VC and the one you want to dismiss to, without changing this code

For a stack of two modals call this baby from your delegate method on the initial presenter to jump back down the stack and nuke the presented VCs.
[self.presentedViewController.presentedViewController dismissViewControllerAnimated:NO completion:nil];
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
Obviously it's a bit brittle because if you start adding more modals then things will break. Generally if you're doing a stack of controllers you would use UINavigationController, but for a couple of modals this does the trick and is a lot less complex than setting up notifications or even more delegates!

Related

objective-c reload UITableView after back navigation

I have a TableViewController embedded in a NavigationController, at some point I use a Segue to push to a new ViewController. When I navigate back from the ViewController I want to reload the tableView in the TableViewController.
I followed the solution posted here and here. For some reason it does not work. I miss something but I can't see it at the moment.
Has anybody a glue, why the code does not work? What do I have to do to get it work?
TableViewController.m
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// notification when comeing back from the cameraView
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(reloadMagazine:) name:#"reloadTable" object:nil];
}
return self;
}
//[...]
// notification reload
-(void)reloadMagazine:(NSNotification *)notification {
NSLog(#"notification reload");
[self.tableView reloadData];
}
//[...]
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadTable" object:self];
}
ViewController.m
-(void)uploadData:(id)sender {
// Upload and navigate back to the tableViewController
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadTable" object:self];
[self dismissViewControllerAnimated:YES completion:nil];
[self.navigationController popViewControllerAnimated:YES];
}
This does not answer your question "why the code does not work?", but the easiest
method to reload the table view when navigating back to it could be to
call reloadData in viewWillAppear.
I suspect that the code above does not work because initWithStyle is not being called. Put a breakpoint there to test my theory. If that is the case, then move that code to viewDidLoad.

Setting UIScrollView's Position from another ViewController

I am trying to set the position of a UIScrollView by using contentOffset as such:
- (void) navigateToTableViewPosition:(CGPoint)contentOffset {
NSLog(#"Position set method gets called...");
NSLog(#"%#", NSStringFromCGPoint(contentOffset));
[mainScrollView setContentOffset:contentOffset animated:YES];
}
I call this method from another view controller before I dismiss it, and everything checks out. I pass the argument correctly, and the method gets called (checked it with NSLog), but the scroll view does not move...
What is funny is that when I call this method from the view controller, in which it is located, it works fine. Only when I call it from another view controller, it stops working.
Just for future reference, here is the calling method:
MainViewController *mainView = [[MainViewController alloc] init];
[mainView navigateToTableViewPosition:contentOffset];
Content offset is a CGPoint I set beforehand. It doesn't matter here; besides, it gets passed correctly anyways.
Try this, You have to send notification from other viewcontroller when you want to change ..
[[NSNotificationCenter defaultCenter] postNotificationName:#"changepostion" object:NSStringFromCGPoint(CGPointMake(contentOffset.x, contentOffset.y))];
in mainviewcontroller
-(void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(navigateToTableViewPosition:) name:#"changepostion" object:nil];
}
- (void) navigateToTableViewPosition:(NSNotification *)notification
{
contentOffset =CGPointFromString([notification object]);
NSLog(#"Position set method gets called...");
NSLog(#"%#", NSStringFromCGPoint(contentOffset));
[mainScrollView setContentOffset:contentOffset animated:YES];
}
You can't set the properties of a view which is not visible. If you are using iOS5+ you can implement the offset setting in the completion in the view dismiss completion block.
Use delegate for backward messaging in view controllers.
Refer Basic Delegate Example link for more reference.
Your are making new instance of viewcontroller which will call method but will have no effect.

Unbalanced calls to begin/end appearance transitions

I'm currently pulling my hair out solving this bug :/ I have already tried the solutions from other SO threads regarding this topic but had no luck so far.
Here's what's wrong:
I have a UINavigationController that pushes View A, from View A I can press a button to push View B - works fine. But when I push View B, then rotate the screen into landscape mode and then click the back button, I get the following output in the console and the view switching is not animated, just switches from B back to A:
2012-01-02 20:50:42.866 [13345:f803] Unbalanced calls to begin/end appearance transitions for <DimensionConversionViewController: 0x68831f0>.
2012-01-02 20:50:42.868 [13345:f803] attempt to dismiss modal view controller whose view does not currently appear. self = <UINavigationController: 0x6b541a0> modalViewController = <UISnapshotModalViewController: 0x6da5190>
This is how I push the View B into the stack:
- (void) showConverter:(id)sender {
[self.navigationController pushViewController:converter animated:YES];
}
-viewDidLoad of View B:
- (void) viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateInterface) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
// ... Update text fields ...
[self updateInterface];
}
-viewDidUnload of View B:
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UIDeviceOrientationDidChangeNotification" object:nil];
}
If you have questions or need more code samples, please let me know.
Thanks in advance for any help :-)
Turned out that in my case the root cause of the problem was, that I forgot to update all the shouldAutorotateToInterfaceOrientation: methods in the different view controllers to return YES for all UIInterfaceOrientations (or let's say they should all return the sam). Doing this solved the issue.

Refresh UIView from Subview

in my universal app, I have a UIControl View inside of a UIScrollView.
On pressing a setup Button, I add another View as subview, like this:
SetupController *setupview = [[SetupController alloc] initWithNibName:#"SetupView-iPad" bundle:nil];
[mainview addSubview:setupview.view];
The subview is displayed like expected.
In this SubView I have some Buttons, which allows the user to switch between settings.
The performed actions are saved in a local Database.
The problem is: On ButtonClick in the SubView, I have to refresh the mainview, to apply the changes. I've tested many ways to make this happen:
In the Subviews class:
[self.parentViewController.view setNeedsDisplay];
No result.
Then I tried to refresh the mainview by notification:
I added this to my subviews classfile, in the function that changes my settings.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:#"setEmoticon_NOTIFICATION" object:self];
}
Then I added the observer to my mainview in the ViewDidLoad method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshView) name:#"setEmoticon_NOTIFICATION" object:nil];
and created the function for this observer:
-(void)refreshView{
NSLog(#"Notification!");
[self.view performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:YES];
}
In Log I get the "Notification!" on changing the settings. But whatever I try, no refresh.
I tried setNeedsDisplay, resignFirstResponder, [self viewDidLoad:] , but still nothing works.
Any ideas how to refresh my mainview?
What exactly do you need to do when refreshing? Do you need to redraw elements on screen, etc?
Do you have a custom refresh method you've implemented in the main view? I would setup a custom protocol in your sub view and before adding the sub view to the main view, I would add the sub view's delegate as the main view
SetupController *setupview = [[SetupController alloc] initWithNibName:#"SetupView-iPad" bundle:nil];
setupView.delegate = mainview;
[mainview addSubview:setupview.view];
Then handle the refresh in the main view call back.
Hell yeah, seems like I found a solution to refresh my superview from subview.
You only have to add the following code snippet to the desired action/method, that should perform the refresh, in your subview controller.
for (UIView* next = [self.view superview]; next; next = next.superview)
{
UIResponder* nextResponder = [next nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
{
[(UIViewController*)nextResponder viewWillAppear:YES];
}
}
In this case I reload the superview, using it's viewWillAppear method.
You can replace it with any method that fits your needs (viewDidLoad, viewWillDissapear etc.).
It may not be the best way to do this, but it works great for me.
Thanks for your answers. ;)

Easy way to dismiss keyboard?

I have quite a few controls scattered throughout many table cells in my table, and I was wondering if there's an easier way to dismiss the keyboard without having to loop through all my controls and resigning them all as the first responder. I guess the question is.. How would I get the current first responder to the keyboard?
Try:
[self.view endEditing:YES];
You can force the currently-editing view to resign its first responder status with [view endEditing:YES]. This hides the keyboard.
Unlike -[UIResponder resignFirstResponder], -[UIView endEditing:] will search through subviews to find the current first responder. So you can send it to your top-level view (e.g. self.view in a UIViewController) and it will do the right thing.
(This answer previously included a couple of other solutions, which also worked but were more complicated than is necessary. I've removed them to avoid confusion.)
You can send a nil targeted action to the application, it'll resign first responder at any time without having to worry about which view currently has first responder status.
Objective-C:
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
Swift 3.0:
UIApplication.shared.sendAction(#selector(resignFirstResponder), to: nil, from: nil, for: nil)
Nil targeted actions are common on Mac OS X for menu commands, and here's a use for them on iOS.
To be honest, I'm not crazy about any of the solutions proposed here. I did find a nice way to use a TapGestureRecognizer that I think gets to the heart of your problem: When you click on anything besides the keyboard, dismiss the keyboard.
In viewDidLoad, register to receive keyboard notifications and create a UITapGestureRecognizer:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(keyboardWillShow:) name:
UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:#selector(keyboardWillHide:) name:
UIKeyboardWillHideNotification object:nil];
tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(didTapAnywhere:)];
Add the keyboard show/hide responders. There you add and remove the TapGestureRecognizer to the UIView that should dismiss the keyboard when tapped. Note: You do not have to add it to all of the sub-views or controls.
-(void) keyboardWillShow:(NSNotification *) note {
[self.view addGestureRecognizer:tapRecognizer];
}
-(void) keyboardWillHide:(NSNotification *) note
{
[self.view removeGestureRecognizer:tapRecognizer];
}
The TapGestureRecognizer will call your function when it gets a tap and you can dismiss the keyboard like this:
-(void)didTapAnywhere: (UITapGestureRecognizer*) recognizer {
[textField resignFirstResponder];
}
The nice thing about this solution is that it only filters for Taps, not swipes. So if you have scrolling content above the keyboard, swipes will still scroll and leave the keyboard displayed. By removing the gesture recognizer after the keyboard is gone, future taps on your view get handled normally.
This is a solution to make the keyboard go away when hit return in any textfield, by adding code in one place (so don't have to add a handler for each textfield):
consider this scenario:
i have a viewcontroller with two textfields (username and password).
and the viewcontroller implements UITextFieldDelegate protocol
i do this in viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
username.delegate = self;
password.delegate = self;
}
and the viewcontroller implements the optional method as
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
and irrespective of the textfield you are in, as soon as i hit return in the keyboard, it gets dismissed!
In your case, the same would work as long as you set all the textfield's delegate to self and implement textFieldShouldReturn
A better approach is to have something "steal" first responder status.
Since UIApplication is a subclass of UIResponder, you could try:
[[UIApplication sharedApplication] becomeFirstResponder]
[[UIApplication sharedApplication] resignFirstResponder]
Failing that, create a new UITextField with a zero sized frame, add it to a view somewhere and do something similar (become followed by resign).
Tuck this away in some utility class.
+ (void)dismissKeyboard {
[self globalResignFirstResponder];
}
+ (void) globalResignFirstResponder {
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
for (UIView * view in [window subviews]){
[self globalResignFirstResponderRec:view];
}
}
+ (void) globalResignFirstResponderRec:(UIView*) view {
if ([view respondsToSelector:#selector(resignFirstResponder)]){
[view resignFirstResponder];
}
for (UIView * subview in [view subviews]){
[self globalResignFirstResponderRec:subview];
}
}
#Nicholas Riley & #Kendall Helmstetter Geln & #cannyboy:
Absolutely brilliant!
Thank you.
Considering your advice and the advice of others in this thread, this is what I've done:
What it looks like when used:
[[self appDelegate] dismissKeyboard]; (note: I added appDelegate as an addition to NSObject so I can use anywhere on anything)
What it looks like under the hood:
- (void)dismissKeyboard
{
UITextField *tempTextField = [[[UITextField alloc] initWithFrame:CGRectZero] autorelease];
tempTextField.enabled = NO;
[myRootViewController.view addSubview:tempTextField];
[tempTextField becomeFirstResponder];
[tempTextField resignFirstResponder];
[tempTextField removeFromSuperview];
}
EDIT
Amendment to my answer to included tempTextField.enabled = NO;. Disabling the text field will prevent UIKeyboardWillShowNotification and UIKeyboardWillHideNotification keyboard notifications from being sent should you rely on these notifications throughout your app.
Quick tip on how to dismiss the keyboard in iOS when a user touches anywhere on the screen outside of the UITextField or keyboard. Considering how much real estate the iOS keyboard can take up, it makes sense to have an easy and intuitive way for your users to dismiss the keyboard.
Here's a link
A lot of overly-complicated answers here, perhaps because this is not easy to find in the iOS documentation. JosephH had it right above:
[[view window] endEditing:YES];
Here's what I use in my code. It works like a charm!
In yourviewcontroller.h add:
#property (nonatomic) UITapGestureRecognizer *tapRecognizer;
Now in the .m file, add this to your ViewDidLoad function:
- (void)viewDidLoad {
//Keyboard stuff
tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapAnywhere:)];
tapRecognizer.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:tapRecognizer];
}
Also, add this function in the .m file:
- (void)handleSingleTap:(UITapGestureRecognizer *) sender
{
[self.view endEditing:YES];
}
Even Simpler than Meagar's answer
overwrite touchesBegan:withEvent:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[textField resignFirstResponder];`
}
This will dismiss the keyboardwhen you touch anywhere in the background.
You should send endEditing: to working window being the subclass of UIView
[[UIApplication sharedApplication].windows.firstObject endEditing:NO];
In your view controller's header file add <UITextFieldDelegate> to the definition of your controller's interface so that it conform to the UITextField delegate protocol...
#interface someViewController : UIViewController <UITextFieldDelegate>
... In the controller's implementation file (.m) add the following method, or the code inside it if you already have a viewDidLoad method ...
- (void)viewDidLoad
{
// Do any additional setup after loading the view, typically from a nib.
self.yourTextBox.delegate = self;
}
... Then, link yourTextBox to your actual text field
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField
{
if (theTextField == yourTextBox) {
[theTextField resignFirstResponder];
}
return YES;
}
The best way to dismiss keyboard from UITableView and UIScrollView are:
tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag
In swift 3 you can do the following
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
Jeremy's answer wasn't quite working for me, I think because I had a navigation stack in a tab view with a modal dialog on top of it. I'm using the following right now and it is working for me, but your mileage may vary.
// dismiss keyboard (mostly macro)
[[UIApplication sharedApplication].delegate dismissKeyboard]; // call this in your to app dismiss the keybaord
// --- dismiss keyboard (in indexAppDelegate.h) (mostly macro)
- (void)dismissKeyboard;
// --- dismiss keyboard (in indexAppDelegate.m) (mostly macro)
// do this from anywhere to dismiss the keybard
- (void)dismissKeyboard { // from: http://stackoverflow.com/questions/741185/easy-way-to-dismiss-keyboard
UITextField *tempTextField = [[UITextField alloc] initWithFrame:CGRectZero];
UIViewController *myRootViewController = <#viewController#>; // for simple apps (INPUT: viewController is whatever your root controller is called. Probably is a way to determine this progragrammatically)
UIViewController *uivc;
if (myRootViewController.navigationController != nil) { // for when there is a nav stack
uivc = myRootViewController.navigationController;
} else {
uivc = myRootViewController;
}
if (uivc.modalViewController != nil) { // for when there is something modal
uivc = uivc.modalViewController;
}
[uivc.view addSubview:tempTextField];
[tempTextField becomeFirstResponder];
[tempTextField resignFirstResponder];
[tempTextField removeFromSuperview];
[tempTextField release];
}
You may also need to override UIViewController disablesAutomaticKeyboardDismissal to get this to work in some cases. This may have to be done on the UINavigationController if you have one.
Subclass your textfields... and also textviews
In the subclass put this code..
-(void)conformsToKeyboardDismissNotification{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissKeyBoard) name:KEYBOARD_DISMISS object:nil];
}
-(void)deConformsToKeyboardDismissNotification{
[[NSNotificationCenter defaultCenter] removeObserver:self name:KEYBOARD_DISMISS object:nil];
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self resignFirstResponder];
}
In the textfield delegates (similarly for textview delegates)
-(void)textFieldDidBeginEditing:(JCPTextField *)textField{
[textField conformsToKeyboardDismissNotification];
}
- (void)textFieldDidEndEditing:(JCPTextField *)textField{
[textField deConformsToKeyboardDismissNotification];
}
All set.. Now just post the notification from anywhere in your code. It will resign any keyboard.
And in swift we can do
UIApplication.sharedApplication().sendAction("resignFirstResponder", to: nil, from: nil, forEvent: nil)
To dismiss a keyboard after the keyboard has popped up, there are 2 cases,
when the UITextField is inside a UIScrollView
when the UITextField is outside a UIScrollView
2.when the UITextField is outside a UIScrollView
override the method in your UIViewController subclass
you must also add delegate for all UITextView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
In a scroll view, Tapping outside will not fire any event, so in that case use a Tap Gesture Recognizer,
Drag and drop a UITapGesture for the scroll view and create an IBAction for it.
to create a IBAction, press ctrl+ click the UITapGesture and drag it to the .h file of viewcontroller.
Here I have named tappedEvent as my action name
- (IBAction)tappedEvent:(id)sender {
[self.view endEditing:YES]; }
the abouve given Information was derived from the following link, please refer for more information or contact me if you dont understand the abouve data.
http://samwize.com/2014/03/27/dismiss-keyboard-when-tap-outside-a-uitextfield-slash-uitextview/
I hate that there's no "global" way to programmatically dismiss the keyboard without using private API calls. Frequently, I have the need to dismiss the keyboard programmatically without knowing what object is the first responder. I've resorted to inspecting the self using the Objective-C runtime API, enumerating through all of its properties, pulling out those which are of type UITextField, and sending them the resignFirstResponder message.
It shouldn't be this hard to do this...
It's not pretty, but the way I resign the firstResponder when I don't know what that the responder is:
Create an UITextField, either in IB or programmatically. Make it Hidden. Link it up to your code if you made it in IB.
Then, when you want to dismiss the keyboard, you switch the responder to the invisible text field, and immediately resign it:
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
You can recursively iterate through subviews, store an array of all UITextFields, and then loop through them and resign them all.
Not really a great solution, especially if you have a lot of subviews, but for simple apps it should do the trick.
I solved this in a much more complicated, but much more performant way, but using a singleton/manager for the animation engine of my app, and any time a text field became the responder, I would assign assign it to a static which would get swept up (resigned) based on certain other events... its almost impossible for me to explain in a paragraph.
Be creative, it only took me 10 minutes to think through this for my app after I found this question.
A slightly more robust method I needed to use recently:
- (void) dismissKeyboard {
NSArray *windows = [UIApplication sharedApplication].windows;
for(UIWindow *window in windows) [window endEditing:true];
// Or if you're only working with one UIWindow:
[[UIApplication sharedApplication].keyWindow endEditing:true];
}
I found some of the other "global" methods didn't work (for example, UIWebView & WKWebView refused to resign).
Add A Tap Gesture Recognizer to your view.And define it ibaction
your .m file will be like
- (IBAction)hideKeyboardGesture:(id)sender {
NSArray *windows = [UIApplication sharedApplication].windows;
for(UIWindow *window in windows) [window endEditing:true];
[[UIApplication sharedApplication].keyWindow endEditing:true];
}
It's worked for me
Yes, endEditing is the best option. And From iOW 7.0, UIScrollView has a cool feature to dismiss the keyboard on interacting with the scroll view. For achieving this, you can set keyboardDismissMode property of UIScrollView.
Set the keyboard dismiss mode as:
tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag
It has few other types. Have a look at this apple document.
In swift :
self.view.endEditing(true)
the easist way is to call the method
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(![txtfld resignFirstResponder])
{
[txtfld resignFirstResponder];
}
else
{
}
[super touchesBegan:touches withEvent:event];
}
You have to use one of these methods,
[self.view endEditing:YES];
or
[self.textField resignFirstResponder];