how to access previous view elements in UINavigationController - iphone

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;
}

Related

Scroll position in Text View object on detail view doesn't reset when returning

I have information in a Master View which uses a UITableView object and a detail ViewContrailer that comes up after I push a user selection to it.
My challenge is with scrolling in a UITextView object, that doesn't go back to the top of the scrolling area on the detail View.
The user chooses a detail object from a list in the Master View Controller, then , brings it up in the detail ViewController. She then scrolls within the UITextView object - dogScrollingInfoTextView- on that view, going farther down so that upper lines don't show anymore. She then return to the Master View Controller, and selects a different row. Upon returning to the detail view, the new object's UITextView object (dogScrollingInfoTextView) is still positioned where the previous view left it.
The object being passed into this UITextView in the detailViewController, looks like this in the Master View Controller
self.detailViewController.dogScrollingInfo = dog.whatMakesDogSpecial;
In the original class definition, this property is declared like this
#property (strong, nonatomic) NSString *whatMakesDogSpecial;
The object used to transfer within the detailView controller class to the view are declared like this
#property (strong, nonatomic) NSString *dogScrollingInfo;
#property (weak, nonatomic) IBOutlet UITextView *dogScrollingInfoTextView;
and in the detailViewController implementation the transfer looks like this
self.dogScrollingInfoTextView.text = [self.dogScrollingInfo description];
I'm not sure if I should attach screen shots ?
Should I put in any other code? (I have put two methods below, one from each view controller class)
I can't find anything specifically related to scrolling in the UITextView Class or the UIView class, that tells me I have to do something programatically. This seems like it may have something to do with InterfaceBuilder. I'm using xib's NOT storyboards. But I can't find any choices in the different Inspectors that change this behavior.
Is configureView, which I use in the detail view controller class, perhaps not the right method to use for this? I think I found an example using the configureView method in the Xcode Master-Detail project template, though I built my application up from a Single View project template. And if not, how would I figure out what method to use?
I'm trying to learn to work with the apple documentation to solve challenges like this, but I find it hard navigating it. So pointers as to how to use the documentation better are much appreciated.
.... Code Snip from the Master View Controller class...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.detailViewController) {
self.detailViewController = [[HardlyWorkingDogsDetailViewController alloc]
initWithNibName:#"HardlyWorkingDogsDetailViewController"
bundle:nil ];
}
NSLog(#"The user selected dog #array position %d", indexPath.row);
Dog *dog = self.sortedDogDictionaryArray[indexPath.row];
self.detailViewController.dogName= dog.dogName;
self.detailViewController.dogLicense = dog.licenseString;
self.detailViewController.dogScrollingInfo = dog.whatMakesDogSpecial;
self.detailViewController.dogPhoto = dog.dogPhoto;
[self.navigationController pushViewController:self.detailViewController animated:YES];
}
.... Code Snip from the Detail View Controller class...
-(void)configureView {
//NSLog(#"ConfigureView # 1 in detail vu");
if (self.dogLicense) {
self.dogLicenseUILabel.text = [self.dogLicense description];
self.dogNameUIText.text = [self.dogName description];
self.dogScrollingInfoTextView.text = [self.dogScrollingInfo description];
self.dogPhotoUIImage.image = self.dogPhoto;
}
}
Thank you
Laurel
Since you are reusing a HardlyWorkingDogsDetailViewController, this behavior makes sense. If HardlyWorkingDogsDetailViewController is light weight enough, there really isn't any need to reuse it. Simply reinstantiating it everytime should fix your problem:
self.detailViewController = [[HardlyWorkingDogsDetailViewController alloc] initWithNibName:#"HardlyWorkingDogsDetailViewController" bundle:nil ];
Dog *dog = self.sortedDogDictionaryArray[indexPath.row];
self.detailViewController.dogName= dog.dogName;
self.detailViewController.dogLicense = dog.licenseString;
self.detailViewController.dogScrollingInfo = dog.whatMakesDogSpecial;
self.detailViewController.dogPhoto = dog.dogPhoto;
[self.navigationController pushViewController:self.detailViewController animated:YES];
Although, having not seen your other logic, I can't say that won't introduce other bugs if any of your code depends on self.detailViewController only being created once (keeping a reference to it elsewhere, for example). If that is the case, or you just don't want to change the MasterViewController code, you can simple put the following in HardlyWorkingDogsDetailViewController:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.dogScrollingInfoTextView setContentOffset:CGPointMake(0, 0) animated:NO];
}
This makes it so every time the view is about to appear on the screen, the scroll view will get set back to the top. (viewDidLoad, viewWillAppear:, viewDidAppear:, viewWillDisappear:, viewDidDisappear: are part of UIViewController lifecycle and are very useful. You should read up on them)
On a side note, UITextView is implemented using a UIScrollView, so any scrolling method a UIScrollView has, the UITextView can do as well. More info here

UIPopover and UITableView data exchange

I have a UITableView in a UINavigationController. On the navigation bar I have a button called add. When this button is pressed it presents a UIPopoverController, where user can input data to be added as a new row/cell in the UITableView. My issue is how can I add a new cell to the UITableView from the UIPopover? Do I pass in the array data to the UIPopOver root controller?
There are two solutions to this that I'm aware of. One would be to send a notification from the popover to the root controller and apply the necessary code to update the tableView in the handleNotification method.
The other, one that I personally use, is to set up a delegate protocol for the popover. You'll have to set it up something like this:
#protocol PopoverDelegate
- (void)addNewCell; // you can add any information you need to pass onto this if necessary such as addNewCellWithName:(NSString *)name, etc.
#end
#interface MyPopoverViewController..... {
id <PopoverDelegate> delegate;
// the rest of your interface code;
}
#property (nonatomic, retain) id delegate;
// any other methods or properties;
#end
Then in your root view controller header file, you need to add the delegate
#interface RootViewController .... <PopoverDelegate> {
Then in your root view controller implementation file, assign the popover delegate when you instantiate it. For example:
MyPopoverViewController *vc = [[MyViewController alloc] init];
vc.delegate = self; // this is where you set your protocol delegate
myPopover = [[UIPopoverController alloc] initWithContentViewController:vc];
myPopover.delegate = self;
[vc release];
Finally, you'll add your protocol method somewhere in the code
- (void)addNewCell {
// do what you want with the tableView from here
}
Sorry that's a bit long. I just wanted to make sure I was thorough. Hope it helps

Reload a table view on close of add subview

I have three objects in the applicaion. There is a UItableviewcontroller(with nib file) that shows a list of the items. One UIviewcontroller to add item (with nib file) and a model class that contains item object.
I show the list of item firstly (on application start). I have a navigation button on navigation bar to pop up add view (add as subview) on the same screen (on table view). In add view I have a add button. when I click on add button it adds the record and disappear from the table view but doesn't reload the that.
I have used following code in add item button click action
listitem *home= [[listitem alloc] initWithNibName:#"listitem" bundle:nil];
[self.navigationController pushViewController:home animated:YES];
[home viewWillAppear:YES];
[home release];
[self.view removeFromSuperview];
In viewwillappear function I am reloading the data from database and also reloading the table view data using reloadData.
Am I doing correct. What is the mistake I am doing.
Your code is very tricky to read* but this is what I think you're doing:
You're making a new list each time that you add an item. you don't want to create a new home object, you want to go back to the last one?
i.e. replace your code with
[self.navigationController popViewControllerAnimated:YES];
and this will go back to your original list, which should have refreshed itself (a UINavigationController will call viewWillAppear for you).
Hope that helps.
NB You have to have used a navigation controller to add your 'add item' view otherwise this code won't work :( This is how you should be adding your item view.
AddItem *home = [[AddItem alloc] initWithNibName:#"AddItem" bundle:nil];
[self.navigationController pushViewController:home animated:YES];
This will slide on your add item view.
If you want the add item view to be a popup, a UINavigationController is definitely not the way to do it!
You will need to tell your initial list view when it needs to update itself. You can do this using either a delegate or a notification. I'd go for a delegate in this case.
You need to add this to your add item view controller's code (AddItem's .h file)
#interface AddItem : UIViewController {
UITableViewController *delegate;
}
#property (nonatomic, assign) UITableViewController *delegate;
#end
and synthesize it in your AddItem's .m file
#synthesize delegate;
When you create your add item view controller, set the delegate to be the initial view controller. i.e.
AddItem *home= [[AddItem alloc] initWithNibName:#"AddItem" bundle:nil];
home.delegate = self;
home.view.frame = CGRectMake(10, 20, 300, 300);
[self.view addSubview:home.view];
Finally, when you have added a new item, tell your delegate to refresh itself like so :
[delegate reloadData];
[self.view removeFromSuperview];
*It's standard practice to use capital letters for class names i.e. listitem should be called ListItem. Personally, I'd call it ItemListViewController so it's clear what it does.

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.