Storing UITextField contents before view pops - iphone

I am sure this is in the Apple documentation or must have been answered somewhere on this forum, since it seems so basic, but I could not find it nor a particularly elegant solution myself.
What I have is a UIViewController that pushes an editing view on its navigation stack. The editing view has a bunch of UITextFields in it. If one of them is being editing when the back button is touched, the original view's ViewWillAppear method is called before either the UITextField delegate methods of textFieldShouldEndEditing or textFieldDidEndEditing, or the IB linked action textFieldEditingEnded method are called.
Here is some code that I hope will make it clearer:
In the UIViewController:
- (void) viewWillAppear: (BOOL) animated {
[super viewWillAppear: animated];
NSLog( #"Entering view will appear for master view" );
nameLabelField.text = objectToEdit.name;
}
- (IBAction) editMyObject: (id) sender {
NSLog( #"Editing the object" );
EditViewController *evc = [[EditViewController alloc] initWithNibName: #"EditTableView" bundle: nil];
evc.editedObject = objectToEdit;
[self.navigationController pushViewController: evc animated: YES];
[evc release];
}
In the EditViewController <UITextFieldDelegate>:
- (void) viewWillAppear: (BOOL) animated {
[super viewWillAppear: animated];
nameField.text = editedObject.name;
}
- (void) viewWillDisappear: (BOOL) animated {
[super viewWillDisappear: animated];
NSLog( #"In viewWillDisappear" );
if( [self.navigationController.viewControllers indexOfObject: self] == NSNotFound ) {
NSLog( #"-- We are not in controller stack... the back button has been pushed" );
}
}
- (BOOL) textFieldShouldEndEditing: (UITextField *) textField {
NSLog( #"In textFieldShouldEndEditing" );
// Store text field value here???
// editedObject.name = nameField.text;
return YES;
}
- (void) textFieldDidEndEditing: (UITextField *) textField {
NSLog( #"In textFieldDidEndEditing" );
// Store text field value here???
// editedObject.name = nameField.text;
}
- (IBAction) textFieldEditingEnded: (id) sender {
NSLog( #"In textFieldEditingEnded" );
// Store text field value here???
// editedObject.name = nameField.text;
}
The log ends up with:
[...] Entering view will appear for master view
[...] Editing the object
[...] In viewWillDisappear
[...] -- We are not in controller stack... the back button has been pushed
[...] Entering view will appear for master view
[...] In textFieldShouldEndEditing
[...] In textFieldEditingEnded
[...] In textFieldDidEndEditing
I want to set self.editedObject.name = nameField.text before the label gets set in viewWillAppear for the UIViewController.
I thought about in the viewWillDisappear method for the EditViewController checking to see if any of my text fields are currently the first responder and if so getting their text and storing it, but this seems like such a kludge that will be a pain to maintain if I add or change text fields.
I can also implement the textFieldEditingChanged IB linked action to set the text in the edited object after every keystroke but this is also quite a bit of overhead since I have to figure out which text field I am in every keystroke (remember I only showed name but there are a whole bunch of them).
All I need is for the editing to be ended or to know the editing will be ended before viewWillAppear is called in the UIViewController so the nameFieldLabel is properly set.

OK, I figured out a simple solution after a lot of web-surfing, forum reading, and manual reading. It was, as I suspected, very simple, only one line of code added. In the viewWillDisappear method of the EditViewContorller I simply added:
[self.view.window endEditing: YES];
Now textFieldShouldEndEditing, textFieldEditingEnded, and textFieldDidEndEditing all get fired off before the viewWillAppear of the master view does.
So now the viewWillDisappear method looks like:
- (void) viewWillDisappear: (BOOL) animated {
[super viewWillDisappear: animated];
NSLog( #"In viewWillDisappear" );
// Force any text fields that might be being edited to end so the text is stored
[self.view.window endEditing: YES];
}
And the methods already in place to handle the 'Return' on the keyboard also handle the 'Back' button on the Navigation controller.
Thank you Aaron and Jeff for your assistance and helping me think this through.

Why not just create your own Back button with that logic in its action method?

I would think that from a UX perspective, you should display an alert to determine if the user wants to cancel the edit action they were in the middle of before exiting the current view.
By alerting the user, you can see if they hit the button by accident or if they did decide to leave the view, take the appropriate action.
// add this to the field(s) to be edited, selector will be called as the changes
// are being made... still difficult to handle a cancel, but should work
[objectToEdit addTarget:self action:#selector(updateNameField:)
forControlEvents:UIControlEventEditingChanged];
additional code here...
// the method called to update object from parent view
- (void)updateNameField:(id)sender {
<OBJECT TO UPDATE>.text = ((UITextField *)sender).text;
}

Related

Hide keyboard on ViewWillAppear

i have a screen having navigation controller and text field. when i move next and come back i want the keyboard should be hidden in first screen. I am hiding keyboard like on textfield event.
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
But how to do that in View related events so that whenever my view appears keyboard is hidden..
Pls guide/Help.
thanks in adv.
I think this is also a good way to remove keyboard with in iOS App if your UITextView or UITextField not connected through the IBOutlet.
If you want to Hide Keyboard with UIViewController LifeCycle Events like with viewWillAppear or etc. Follow this
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[self view] endEditing:YES];
}
Otherwise if you object connected using IBOutLet this code will work fine as you describe too.
[yourTextField resignFirstResponder];
Add this code to your ViewWillAppear :
for(id obj in self.view.subviews)
{
if([obj isKindOfClass:[UITextField class]])
{
[obj resignFirstResponder];
}
}
This would take in all the textfields in that particular view here it is the whole view and add the code you had written previously for removing the keyboard.
A good habit is to write this code in your screen's -viewWillDisappear. So, when you navigate from one screen to another at that time it will remove the keyboard from that screen.
- (void)viewWillDisappear:(BOOL)animated
{
[self.view endEditing:YES];
[super viewWillDisappear:animated];
}
For multiple textFields, it is better to use -endEditing for that particular view instead of -resignFirstResponder for any single textField. Take a look at my Answer.
//This is for Swift
override func viewWillAppear(animated: Bool)
{
self.view.endEditing(true)
}
The thing that you are doing wrong is , when you are moving back previous controller to the current controller , the keyboard is up due to the selected textfield of previous controller .
And in the current controller the code:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[self view] endEditing:YES];
}
It will not work as no textfield is selected at this controller. So what you need to do is write the same code in the previous controller viewWillDisappear Method it will surely resolve your Problem .
- (void)viewWillDisappear:(BOOL)animated
{
[self.view endEditing:YES];
[super viewWillDisappear:animated];
}

Having object send its own delegate a message after its been removed from superview

I have the following method:
- (void) okButtonPushed
{
if ([self.delegate respondsToSelector:#selector(alertViewWillDismiss:)])
{
[self.delegate alertViewWillDismiss:self];
}
[self removeFromSuperview];
}
It's an OK button on a custom AlertView object that when pushed dismisses the alert. I'd like for the delegate to send a message AFTER the alert has been removed from the superview, but obviously this isn't possible since it will be deallocated by then. The only other solution I can think of this is for the superview to keep another pointer to my object, but then everytime I do an alert, I have to implicitly know to make sure to keep another pointer to it. Is there a cleaner way to do this from within my AlertView class?
You can use the willMoveToSuperview: callback (
-(void)willMoveToSuperview:(UIView *)newSuperview {
if (newSuperview == nil) {
if ([self.delegate respondsToSelector:#selector(alertViewWillDismiss:)]) {
[self.delegate alertViewWillDismiss:self];
}
}
}
It will be called before the view is deallocated, and just before it's been truly removed from the superview (with newSuperview == nil).
EDIT: If you want it to be just after it's been (re)moved – use didMoveToSuperview:

Overlay View with Subview, don't know calling VC

I need a view (infoView) to be displayed as an overlay on top of another view. As this infoView should be callable from every view of the app(e.g. introView), I'd like the code to be in the infoViews VC and just call its methods when an action at the currentView (introView) happens. I can't use push and pop, as I need to change the background color (infoView) and especially the alpha of the calling view (introView), so right now I do it with insertSubview.
My Code by now:
introVC .h
- (IBAction) openInf:(id)sender;
IBOutlet InfoVC *infoScreenVC;
introVC .m
- (IBAction) openInf:(id)sender {
[infoScreenVC openInfoMethod];}
infoVC .h
- (IBAction) closeInfoPressed;
- (void) openInfoMethod;
- (void) closeInfoMethod;
infoVC .m
- (IBAction) closeInfoPressed {
[self closeInfoPressed];}
- (void) closeInfoMethod {
[self.view removeFromSuperview];
[self.xx.view setAlpha:1.0f];}
- (void) openInfoMethod {
self.view.backgroundColor = [UIColor clearColor];
[self.xx.view setAlpha:0.2f];
[((MyAppAppDelegate *)[UIApplication sharedApplication].delegate).window
insertSubview: self.infoScreenVC.view aboveSubview: self.xx.view];}
When I push the button to show the infoView, my NSLogs tell me the method was called, but I can see the Subview wasn't added. I have absolutely no clue what to insert where right now it says xx in my code, as a VC reference from intro doesn't show me the screen.
If I put that code in introVC an modify it, it does show the infoView, calls the correct method to close, but again can't close (when I'm in introVC). I can't figure out how to tell my app who was the calling VC to get back there.
At some point, when all the code was in introVC I managed to even remove the Subview, but couldn't set the Alpha of introVC back to one.
I do struggle with that since two days.. -.- Or is there maybe an easier solution even?
Thank you very much!
//Edit after sergios answer:
intro.m
- (IBAction) openInf:(id)sender {
introViewController *introVC;
[infoScreenVC openInfoMethod:];}
info.h
- (void) openInfoMethod:(introViewController *introVC);
info.m
- (void) openInfoMethod:(introViewController *introVC) { //error occurs here
self.view.backgroundColor = [UIColor clearColor];
[self.introVC.view setAlpha:0.2f];
[((MyAppAppDelegate *)[UIApplication sharedApplication].delegate).window
insertSubview: self.infoScreenVC.view aboveSubview: self.introVC.view];}
and the occurring error says
Expected ')' before 'introVC'
I'm not sure how to pass the VC reference properly.
Thank you for your help!!
//EDIT Working Code:
As it works now, I'd like to sum things up:
- I give the calling VC (introVC) to openInfoMethod on the Action openInf like [infoVC openInfoMethod:introVC].
In openInfoMethod I "save" the calling VC in a local variable of type introVC (?) and add the overlay etc.
When the Action of the infoViewController named closeInfoPressed occurs, it calls infoViewController's method closeInfoMethod like self closeInfoMethod:introVC.
In that method I remove self.view from Superview and set introVC.view's Alpha to 1 like introVC.view setAlpha:1.0f
So the codesnippets are
intro.h
IBOutlet InfoscreenViewController *infoScreenVC;
#property (nonatomic, retain) IBOutlet InfoscreenViewController *infoScreenVC;
- (IBAction) openInf:(id)sender;
intro.m
#synthesize infoScreenVC;
- (IBAction) openInf:(id)sender {
UIViewController *introVC = self;
[infoScreenVC openInfoMethod:introVC];
}
info.h:
- (void) openInfoMethod:(UIViewController *)rootVC;
- (void) closeInfoMethod:(UIViewController *)callingVC;
info.m
- (void) closeInfoMethod:(UIViewController *)callingVC;{
[self.view removeFromSuperview];
[callingVC.view setAlpha:1.0f];
}
- (IBAction) closeInfoPressed{
[self closeInfoMethod:introVC];
[self.view removeFromSuperview];
}
If your problem is "figure out how to tell my app who was the calling VC to get back there", why don't you add a parameter to openInfo selector, like here:
info.h
- (void) openInfoMethod:(introViewController *)introVC;
info.m
- (void) openInfoMethod:(introViewController *)introVC {
<your implementation here>
}
would this work for you?
EDIT: your code from intro.m has got a small problem,
- (IBAction) openInf:(id)sender {
introViewController *introVC;
[infoScreenVC openInfoMethod:];
}
indeed, you are not initializing your introVC variable, so that when you pass it into -openInfoMethod: it will have some weird value and cause a crash.
As far as I can grasp from your code, intro.m should be the implementation file for your introViewController, therefore you can simply call:
[infoScreenVC openInfoMethod:self];
but please, before doing this, confirm that self is actually your introViewController.
This should be ilke this
(void) openInfoMethod:(introViewController *)introvc you are passing parameters in a wrong way.
If I understand your question correctly, your infoView should be a subclass of UIView and instantiated from any of your view controllers using:
InfoView *infoView = [[InfoView alloc] initWithFrame:CGRectMake(originX,originY,width,height)];
Then you simply add it as a subview of your view controllers view:
[self.view addSubView:infoView];
[infoView release]; // It's safe to release it here as your view controller's view is retaining it
And when you are done with it, simply call
[infoView removeFromSuperview];
As an aside, you could create some simple methods inside infoView that include introducing animation when the view is presented or removed. Here's an example for fade in and out which assume you set the alpha to zero initially when you create the view:
- (void)fadeIn {
[UIView animateWithDuration:1.0f animations:^{self.alpha = 1.0f}];
}
And fade out
- (void)fadeOut {
[UIView animateWithDuration:1.0f animations:^{self.alpha = 0f}
completion:^(BOOL finished){self removeFromSuperView}];
}

Refresh UINavigationController?

I have a UINavigationController with two ViewControllers on the stack. At a certain point in the program execution, the second view controller is visible on the screen and at that moment, I would like to replace that ViewController with another. However, it's not working. Here is my code:
UINavigationController * thisNavController = self.waitingController;
// remove the Dummy and set the new page instead
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: thisNavController.viewControllers];
[newControllers replaceObjectAtIndex: ([thisNavController.viewControllers count] - 1) withObject: page];
NSLog (#"visible before: %#", [thisNavController.visibleViewController description]);
[thisNavController setViewControllers: [NSArray arrayWithArray: newControllers] animated: YES];
NSLog (#"visible after: %#", [thisNavController.visibleViewController description]);
[thisNavController.visibleViewController.view setNeedsDisplay];
The above code produces this output:
2011-05-05 13:30:22.201 myApp[3286:207] visible before: <DummyViewController: 0x4c8b4c0>
2011-05-05 13:30:22.209 myApp[3286:207] visible after: <RealViewController: 0x60173f0>
But what is shown on the screen does not change. It seems that everything works fine after I switch tabs, so it seems that it is a redrawing problem, but setNeedsDisplay does nothing and I couldn't find a method that tells the NavigationController that its viewControllers have changed.
Is there some refresh mechanism that I have to trigger to refresh the screen?
One solution would be to say add 2 (initial) view controllers when your app is started, and only allow navigation from the 2nd and 3rd ones, falling back to the 1st (root) view controller in your senario described. You never allow navigation back to this 1st view controller or from this 1st view controller to the 2nd; you see this sort of behaviour in some of Apple's apps, like iTunes and Remote - if there's no network connect the app shows a no-network connection view immediately.
So, when you want to show the 1st view controller above, you do something like:
NSArray *array = [navigationController popToRootViewControllerAnimated:NO];
Without more info about the navigation behaviour of your app I hope this helps.
Or show a modal view controller?
The problem turned out to be the fact that I was trying to replace the view controller stack before the initial transition animation for the Dummy controller has finished. This can be prevented in the following manner.
First, preserve the (eventual) delegate, set the current object as the delegate, set a flag that animation is in progress and push the new controller:
self.oldNavigationControllerDelegate = self.waitingController.navigationController.delegate;
self.waitingController.navigationController.delegate = self;
self.isAnimating = YES;
[viewController.navigationController pushViewController: [[DummyViewController alloc] init] animated: YES];
Then, implement the UIViewControllerDelegate protocol methods as follows:
#pragma mark -
#pragma mark UINavigationControllerDelegate methods
- (void) navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (navigationController == self.waitingController.navigationController)
self.isAnimating = YES;
}
- (void) navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (navigationController == self.waitingController.navigationController) {
self.isAnimating = NO;
if (self.readyPage != nil)
[self pageIsReady: self.readyPage]; // method to load the ready controller
}
}
After that, whenever your content/controller/download/whatever is ready, make sure that the navigation controller is no longer animating. If it is, set a flag that the page is ready. If it isn't, load the page:
if (self.isAnimating)
self.readyPage = controller;
else
[self pageIsReady: controller];
And, of course, implement the actual loading of the new stack (as usual):
- (void) pageIsReady: (UIViewController *) page {
// this method should replace the dummy that is spinning there
UINavigationController * thisNavController = self.waitingController.navigationController;
// remove the Dummy and set the new page instead
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: thisNavController.viewControllers];
[newControllers replaceObjectAtIndex: ([thisNavController.viewControllers count] - 1) withObject: page];
thisNavController.viewControllers = [NSArray arrayWithArray: newControllers];
thisNavController.delegate = self.oldNavigationControllerDelegate; // restore the original delegate
// clean up
self.isAnimating = NO;
self.readyPage = nil;
self.waitingController = nil;
self.oldNavigationControllerDelegate = nil;
}
This makes everybody happy :P

How do i check whether my current navigationController.view = a classes.view? Reason = push notifications. + iphone

so basically in my app delegate i have a navigation.controller
This navigation controller has a view of a class named MainScreen.
In MainScreen.m , i have a IBAction which will bring me to a SelectionScreen.m page by pushing it. here is the coding for it
SelectionScreen *aSelectionScreenViewController = [[SelectionScreen alloc]initWithNibName:#"SelectionScreen" bundle:nil];
[self.navigationController pushViewController:aSelectionScreenViewController animated:YES];
[aSelectionScreenViewController release];
So how do i check if my current navigationController.view = this selectionscreen.view?
The reason for checking which current view it is, is because when i receieve a push notification, i would want to automatically switch to this SelectionScreen.m page and invoke some methods within it. But this checking can only be done in the appDelegate because the didReceiveRemoteNotification method is located in there.
This is how i'm doing it
for example if you have three ViewControllers ,and any of those have possibility to be pushed by NavigationController:
ViewControllerA
ViewControllerB
ViewControllerC
Then what you need to do is:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
if ([[self.navigationController topViewController] isKindOfClass:[ViewControllerA class]]) {
//do sth
}
if ([[self.navigationController topViewController] isKindOfClass:[ViewControllerB class]]) {
//do sth
}
if ([[self.navigationController topViewController] isKindOfClass:[ViewControllerC class]]) {
//do sth
}
}//end of code
One way is to save selectionScreenViewController as a property of your app delegate, then:
if (self.navigationController.topViewController == self.selectionScreenViewController) {
//...
}
else {
//...
}
Hey guys, i did it in a simple way. In every view controller i had, i removed all objects and assigned an object to an array in the appdelegate. So this way, everytime i go to a new view, the value is different.
So in appdidrecieveremotenotification, i can check that array and decide on what to do accordingly.
Its just a simple way of checking.