Dismiss ViewController from TableViewController - iphone

I have presented the viewController which contains a navigation controller whose view is loaded from another nib with the UITableView in it. I would like to dismiss that presented viewController from the UITableViewController. I have tried every combination of self.parentViewController and self.navigationController and self.navigationController.parentViewController but I am still not able to dismiss it. How and what is the best way for it to be dismissed?

I have never gotten self.navigationController.parentViewController to work as advertised, but this does work for me:
NSArray *viewControllerArray = [self.navigationController viewControllers];
int parentViewControllerIndex = [viewControllerArray count] - 2;
[[viewControllerArray objectAtIndex:parentViewControllerIndex] myParentViewMethod:arg1 withArg2:arg2 ...];

Is this not working?
[myViewController presentModalViewController:otherController animated:YES];
// ...
[myViewController dismissModalViewControllerAnimated:YES];

It sounds like you've painted yourself into a corner a bit with your hierarchy, but that's ok I think we can fix it. There the two "usual" ways of doing this are:
From controller A, present controller B, from B do work, from B dismiss, return to A
From controller A, present controller B, from B do work, raise event via delegate protocol to A, from A dismiss B which returns control to A
In your current situation, this may require an uncomfortable ammount of refactoring, in which case it may be easier to just expose a handle to the other controller as a property, just make sure it's a weak reference like so:
#interface ControllerB : UIViewController
{
ControllerA* controllerA;
}
#property (readwrite, nonatomic, assign) ControllerA* controllerA; // weak reference
#end
Or however you like, the point is to scope the variable properly so that it's available when you need it.

I just ended up using delegates to dismiss the parent.

Related

ViewDidLoad executes slowly while pushing viewcontroller

I was trying to push a viewcontroller B into navigation controller from A and then assigning some properties of B in A.
In this case, the assigning of properties was done and then viewDidLoad of viewcontroller A was executed.
Here, assigning properties in A should be done only after viewDidLoad of A has done.
For example,
[b.navController pushViewController:a animated:YES];
a.status = #"loaded";
Here, status was assigned first and then viewDidLoad of A was executed.
This happens only in iOS 7 whereas in iOS6 it works fine.
Can anyone please let me know where the problem is?
UPDATE: For me in some cases in iOS7, Push view is not working. How cna I debug and fix it?
Just access the viewcontroller.view (set any thing immediately after the alloc) property after the alloc init;
Which will loadview/viewdidload.
See Apple Documentation
In my experience, a UIViewController view is loaded lazily, no matter which iOS version you're working on. If you want to trigger a view load, and therefore its UIViewController viewDidLoad, you should access the view after the UIViewController is allocated. For example:
UIViewController *aViewController = [[CustomViewController alloc] init];
[aViewController view];
Make sure you don't code it as
aViewController.view
since that would generate a compiler warning.
So, in your case it would have to be something like this:
...
CustomViewController *a = [[CustomViewController alloc] init];
[b.navController pushViewController:a animated:YES];
[a view];
a.status = #"loaded";
Let me know if you have further problems with it.
You can know when a View Controller has been pushed onto the stack by implementing the UINavigationControllerDelegate and setting yourself as the delegate self.navigationController.delegate = self; then you will get this callback after every push
navigationController:didShowViewController:animated:
So you can check if the shown viewController is the one your interested in, then set your a.status.
I would suggest you call a delegate method once the view is loaded.
Set the delegate to be controller B.
and after viewDidLoad finishes (in controller A) call the delegate method. You can even pass parameters as you wish to the delegate.
Here's some example code:
Controller B:
a.delegate = self;
[b.navigationController pushViewController:a animated:YES];
Implement the delegate method:
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status
{
a.status = status;
}
Controller A .h file:
#class ControllerA;
#protocol ControllerADelegate <NSObject>
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status;
#end
#interface ControllerA : UIViewController
#property (nonatomic, weak) id <ControllerADelegate> delegate;
Controller A .m file:
- (void)viewDidLoad:(BOOL)animated
{
[super viewDidLoad:animated];
/* your viewDidLoad code here... */
if ([_delegate respondsToSelector:#selector(controllerIsLoaded:status)])
[_delegate controllerIsLoaded:self status:#"Loaded"];
}
Turn off animation for ios7, in my case its work
[b.navController pushViewController:a animated:NO];
a.status = #"loaded";
No documentation provides enough information to know exactly when viewDidLoad would be called.
UIViewController's documentation just says this
This method is called after the view controller has loaded its view hierarchy into memory
I would suggest that you create a custom initializer like this
- (id)initWithStatus:(NSString *)status {
}
But, if you are trying to use this variable to check if the viewController's view has 'loaded' or not, it may not be possible to do that because the pushViewController or presentViewController methods are not guaranteed to be synchronous.
Even in iOS 6, there was no explicit guarantee that the view would be loaded as soon as that method returned.
Please write the code in viewWillAppear method instead of viewDidLoad in next class i.e. where you are pushing the object to
-(void)viewWillAppear:(BOOL)animated
{
}
I'm understand of your question like this:
B *b = [[B alloc] init];
b.status = #"loaded";
[self.navigationController pushViewController:b animated:Yes];
If you want to pass a value from one controller to another means, you have to assign a value before using pushViewController method.

how to access previous view elements in UINavigationController

I have a UINavigationController in which I am loading different view controllers. I want to know how can i access the elements (like labels etc) of my previous view.
Here is an eg.
View A
myLabel.text = #"first view";
(User moves to view B)
View B
(user entered a message, that i need to display in View A)
something like ViewA.myLabel.text = #"user entered message"
I tried many things but was not able to find anything very useful. Please help..
I am using Xcode 4 without ARC and without storyboard.
Thanks
Sam
Edited:
I want to update the property declared in viewController of View A and not the labels directly. My labels get updated using that property. Like while pushing the viewController we can pass the values as below.
ViewA *myView = [[ViewA alloc] init];
myView.title = #"View B" ;
myView.tableView.tag = 3;
myView.myTextView.text = #"Some Text";
[self.navigationController pushViewController:myView animated:YES];
[myView release];
Is there any way to pass these values to properties of ViewController of ViewA while popping ViewB and returning back to ViewA ?
The actual scenario is as follows: the user gets and option to write a message in textView or he can use the predefined templates. If he clicks on the templates button he is taken to a list of predefined templates where he can select any of the predefined message. Now I want that when the user click on any of the predefined message the view containing the list of predefined message gets popped of and the message he selected gets automatically populated in the textView of main view. what is the best approach to achieve this ?
TIA
Sam
You should set your AViewController as the delegate of your BViewController so you can message it back after a particular event. Using a delegate will also allow better decoupling of your ViewControllers.
In your BViewController, define a protocol like this :
BViewController.h :
#protocol BViewControllerDelegate <NSObject>
- (void)viewB:(UIViewController *)didEnterMessage:(NSString *)message;
#end
and add a delegate property :
#property (nonatomic, weak) id <BViewControllerDelegate> delegate;
When the user enter the message in your BViewController and hit the button that pops the BViewController to show to AViewController do this :
- (IBAction)messageEntered {
if ([self.delegate respondsToSelector:#selector(viewB:didEnterMessage:)]) {
[self.delegate viewB:self didEnterMessage:self.yourTextField.text];
}
}
Your AViewController should implement the BViewControllerDelegate protocol like this :
AViewController.h :
#interface AViewController <BViewControllerDelegate>
When your AViewController creates the BViewController, it should set itself as its delegate before presenting it. Might look like this :
BViewController *bvc = [[BViewController alloc] init…];
bvc.delegate = self;
And finally, your AViewController should implement the viewB:didEnterMessage: method :
- (void)viewB:(UIViewController *)didEnterMessage:(NSString *)message {
self.myLabel.text = message;
}
That's the cleanest way to do that, IMHO.
You can get the navigation controller's viewControllers property and use it, perhaps like this:
UILabel *label = ((SomeViewController *)[self.navigationController.viewControllers objectAtIndex:1]).myLabel;
However, that is not reliable. Since the “previous” view is off the screen, the system can unload it to free up memory. Then label will be nil.
You could force that other view controller to reload its view (if it has been unloaded) by accessing the view controller's view property.
But really this smells like bad design. You should almost never try to access the views of a view controller when that view controller's view is not on screen. Remember how the system can unload a view controller's view if the view is off-screen? If some UILabel under that view contained the only copy of important data, that data is now gone!
Any important data needs to be stored somewhere other than a view - perhaps in a property of the view controller, or in a model object. You should ask the view controller for the data, or for the model object that contains the data. A view controller's view objects should almost always be considered a private implementation detail of the view controller, not exposed to other classes.
EDIT
Your question is puzzling because you talk about popping ViewB and returning to ViewA, but your code only creates and pushes a ViewA. ViewB is not mentioned in the code.
I will assume that your ViewA creates and pushes a ViewB. So you should give ViewB a property of type ViewA, like this:
#class ViewA; // forward declaration to avoid circular imports
#interface ViewB
#property (weak, nonatomic) ViewA *aView;
Then, when your ViewA creates a ViewB instance, you set the aView property:
#implementation ViewA
- (void)pushViewB {
ViewB *bView = [[ViewB alloc] init];
bView.aView = self;
[self.navigationController pushViewController:bView animated:YES];
}
Now your ViewB has access to the ViewA that created it, and can set the properties of that ViewA.
If you want to write a good code you should follow the Model-View-Controller pattern. Here's rather good tutrial http://www.cocoalab.com/?q=node/24 In a couple of words it means that you should not store data in View (and also a view should not act as controller). I suggest you to write a custom class that will do this management(store data and pass it from one view to another).
If it's just a test app then you can use viewControllers property of UINavigationController to access the controllers which are in navigation stack or just create a variable to store this data for example, in View B
- (void)textFieldDidEndEditing:(UITextField *)textField {
stringToDisplayInFirstController = textField.text;
NSArray * arrayOfControllers = self.navigationController.viewControllers;
UIViewController * viewControllerA = [arrayOfControllers objectAtIndex:[arrayOfControllers count]-1];
viewControllerA.label.text = stringToDisplayInFirstController;
}

Assignment in objective c

I have two views, the parent view opens a pop over that has a child view.
In child controller
#property (nonatomic, assign) ParentController *parent;
In parent controller
ChildController *child = [[ChildController alloc] init];
child.parent = self;
My question is in the dealloc method of the child controller, do you set self.parent = nil or release?
This has a bad code smell. It doesn't make much sense and I'm surprised it compiles:
ChildController *child = [[ParentController alloc] init];
And I'm not sure what you mean by "pop over" -- that term has a specific meaning now in iOS (see: Consider Using Popovers for Some Modal Tasks in the iPad Human Interface Guidelines).
Your question "in the dealloc method of the child controller, do you set self.parent = nil or release?" can't be answered properly, as it's also a bad code smell. There's no reason for a child view controller to be fiddling with any reference to a parent view controller like that.
(Although some people have answered your question while I was typing this up, I think you have some design problems that need to be acknowledged)
How are you presenting your "ChildView"? Modally? If so, your code might look something like this:
- (void)showChildView
{
ChildViewController* childViewController = [[ChildViewController alloc] initWithNibName:#"ChildView" bundle:nil];
childViewController.delegate = self;
childViewController.someProperty = #"Some Value";
[self.navigationController presentModalViewController:childViewController animated:YES];
[childViewController release];
}
You should then create a delegate protocol with your ChildViewController class that your ParentViewController class will implement, so it knows when the ChildViewController is finished so it can deal with removing the view appropriately.
Generally, the idea of the ChildViewController needing a pointer back to the ParentViewController is a bad code smell because it sets up a circular dependency.
Set it to nil (or do nothing at all). Releasing would be wrong because your property doesn't retain (it just assigns).
Why do you need a property for the parent view controller anyway? UIViewController already contains a property called parentViewController, so you don't need to define another one.
But if you must do this, you should:
Use retain instead of assign in your property declaration.
Use [self.parent release] in your dealloc method in your child view controller.

How to access data from one view to another view?

I have an UITabBarController with two tabs:
UINavigationController
OptionsViewController : UIViewController
How can I reach data (ie. UILabel.text) set in OptionsViewController, in a new added modal View which has been invoked from UINavigationController?
Edit1:
Scenario: After launching app I select the second tab bar called "Options" where I fill up a textField. The label is set to value from textField. Next I select first tab bar called "Main" where I have a button. I click the button and new modal View appears. In this new modal View I'd like to show the value from textField
I love MVC, but I'm not an absolute purist to the point of hurting yourself to accomplish a fairly trivial task, so the answers you've gotten here are good and useful. However, by creating an ivar to refer back to a specific type such as a label or other view controller, you are coupling things together that aren't necessary to couple. What you could do instead is make your first tab view controller a delegate of your second tab view controller. So do something like this in your app delegate.
OptionsViewController *optionsViewController = // ... get this from the tab view
FirsTabViewController *firstTabViewController = // ... same here
[optionsViewController setDelegate:firsTabViewController];
Which means that you need an ivar in your OptionsViewController:
#property (assign) id delegate;
Then, when whatever event you want to trigger the change occurs in your options view controller, see if the delegate can respond to a selector you've named. For example:
- (void)someEventHappenedLikeTyping:(id)sender;
{
if ([delegate respondsToSelector:#selector(setOptionsString:)]
[delegate performSelector:#selector(setOptionsString:) withObject:[label text]];
}
Notice you never specified any specific object types. You just check to see if the delegate (which was declared as id) can respond to that selector. If it can, it does what it's told and just is silent otherwise.
For this to work, you need an ivar for the optionsString in your FirstTabViewController and so it would be declared in the header as:
#property (copy) NSString *optionsString;
and then #synthesize it in the .m. This causes -setOptionsString to become a valid selector that will get called in the -someEventHappenedLikeTyping method.
Anyhow, now, if you ever need to to change which view controller references which, you don't have to go into the header and change the type of ivar referenced. You simply need to implement the selector (this is known as an informal protocol, by the way) in the view controller that is a delegate of your options view controller.
Just some food for thought there. Hope that helps. There is further de-coupling that could be done in the code I've added, but again it may be overkill for such a simple task. Let me know if you need clarification or want to understand what I mean by further decoupling.
Best regards,
p.s. Sometimes needing to share data between two tab bar view controllers, means you have a design flaw. If you are wanting to store preferences from your options view, you should just call
[[NSUserDefaults standardUserDefaults] setObject:[label text] forKey:#"option1"];
[[NSUserDefaults standardUserDefaults] synchronize];
Then you can pull from the NSUserDefaults back in your main tab with;
NSString *option1 = [[NSUserDefaults standardUserDefaults] objectForKey:#"option1"];
// Do something with option1
In your OptionsViewController, create a property:
#property (nonatomic, retain) UILabel *mylabel;
then after creating your OptionsViewController, but before displaying it, set the mylabel property. (Or perhaps you just want the text, so you can use an NSString* property.)
Edit:
So you probably want to do something like this:
OptionsViewController *vc = [[OptionsViewController alloc] init];
vc.mylabel = mySomethingLabel;
[self presentModalViewController:vc animated:YES];
So after creating the object, you set the property, and then you display the view controller.
I usually set IBOutlets in each of my viewcontrollers which point to the other controller.
So if I had view controllers A and B. A has an IBOutlet to B and B to A. Then whenever I want to access anything in B from A i just use a dot operator on B.
In your example UINavigationController would #include "OptionsViewController.h" and have an ivar IBOutlet OptionsViewController * ovc (which is set in IB) and then any instance variable from your options view controller can be referenced as ovc.UILabel.text from the navigation controller. This process can be reversed to access values from your navigation controller in your options view controller.
Example Navigation Controller (.h):
#include "OptionsViewController.h"
#interface UINavigationController // (whatever the name of this class is)
{
OptionsViewController * ovc;
}
#property (nonatomic, retain) IBOutlet OptionsViewController * ovc;
#end
Example OptionsViewController.h:
#interface OptionsViewController
{
UILabel * label;
}
#property (nonatomic, retain) IBOutlet UILabel * label;
#end
Then from UINavigationController (.m) you can just write ovc.label.text to access the text.
I have an easy way to access data between views. Let's try. You have two views named view1 and view2, you can define a View *vc1 property in view2.h, set the vc1 point to view1, when pop the view2, like this:
view1.m
//here pop out view1 code
View2 *view2 = [[View2 alloc] initWithNibName:#"View2" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:view2 animated:YES];
view2.vc1 = self; //transfer view1 instance to view2,use vc1 you may handle view1 in view2 directly
[view2 release];
view2.h
#import "view1.h"
#property (nonatomic, retain) IBOutlet View1 *vc1; //here you could name it as view1 as well :)
view2.m
vc1.lblTable.text = #"ok"; //you'll see "ok" in view1
[self.navigationController popViewControllerAnimated:YES]; //navigate to view1
//dont forget release vc1

Pop-up modal with UITableView on iPhone

I need to pop up a quick dialog for the user to select one option in a UITableView from a list of roughly 2-5 items. Dialog will be modal and only take up about 1/2 of screen. I go back and forth between how to handle this. Should I subclass UIView and make it a UITableViewDelegate & DataSource?
I'd also prefer to lay out this view in IB. So to display I'd do something like this from my view controller (assume I have a property in my view controller for DialogView *myDialog;)
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"DialogView" owner:myDialog options:nil];
myDialog = [nibViews objectAtIndex:0];
[self.view addSubview:myDialog];
problem is i'm trying to pass owner:myDialog which is nil as it hasn't been instantiated...i could pass owner:self but that would make my view controller the File's Owner and that's not how that dialog view is wired in IB.
So that leads me to think this dialog wants to be another full blown UIViewController... But, from all I've read you should only have ONE UIViewController per screen so this confuses me because I could benefit from viewDidLoad, etc. that come along with view controllers...
Can someone please straighten this out for me?
There is no such thing as a view controller being on the screen; its view is on the screen. With that said, you can present as many views as you want on the screen at once.
I would create a new view and view controller. You would not make a UIView be a UITableViewDelegate, you make a UIViewController be a UITableViewDelegate. But instead of doing that manually, instead make your new view controller a subclass of UITableViewController, if you're using iPhone OS 3.x+. You can then present this view controller modally.
You probably want to give the user a chance to cancel out of the selection. A good way to do that is to wrap your new dialog view controller in a UINavigationController and then put a "Cancel" button in the nav bar. Then use the delegate pattern to inform the parent view controller that the user has made their choice so you can pop the stack.
Here's what the code will look like inside your parent view controller, when you want to present this option dialog:
- (void)showOptionView
{
OptionViewController* optionViewController = [[OptionViewController alloc] initWithNibName:#"OptionView" bundle:nil];
optionViewController.delegate = self;
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:optionViewController];
[self.navigationController presentModalViewController:navController animated:YES];
[navController release];
[optionViewController release];
}
Your OptionViewController .h will look like this:
#protocol OptionViewControllerDelegate;
#interface OptionViewController : UITableViewController
{
id<OptionViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<OptionViewControllerDelegate> delegate;
#end
#protocol OptionViewControllerDelegate <NSObject>
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSString*)selection;
// or maybe
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection;
// etc.
#end
Your OptionViewController.m will have something like this:
- (void)madeSelection:(NSUInteger)selection
{
[delegate OptionViewController:self didFinishWithSelection:selection];
}
Which has a matching method back in your original view controller like:
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection
{
// Do something with selection here
[self.navigationController dismissModalViewControllerAnimated:YES];
}
There are plenty of examples throughout Apple's sample source code that follow this general pattern.