Reload a table view on close of add subview - iphone

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.

Related

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

iOS4 - custom uiview accessing UINavigation stack

Bit confused with this one so bear with me...
I have a Navigation-based project which is working fine. I'm trying to create my first custom UIView to make a couple of buttons which I will use in multiple places. One of the buttons needs to push a viewcontroller into the navigation when it's clicked but I'm not sure how to do this.
When I had the button set up within a view controller I was using:
LocationViewController *controller = [[LocationViewController alloc] initWithNibName:#"LocationViewController" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
but the self.navigation controller won't work now, will it? How do I access the navigation controller of the viewcontroller that this uiview will be added to?
Hope at least some of that makes sense, as I said it's my first go at subclassing the uiview and adding it to multiple pages so I'm a bit lost.
EDIT TO ADD - I have the button click events inside the custom UIView, so that is where I'm trying to change the viewcontroller from. Should I instead wire up the events in whichever viewcontroller I add the view to?
Usually your appDelegate has a UINavigationController property. You can access it in your custom view like this:
UINavigationController *navController = (MyAppDelegate *)[[[UIApplication sharedApplication]
delegate] navigationController];
But more effective way is to make delegate method for your custom view and handle button action in your viewController.
MyCustomView.h
#protocol MyCustomViewDelegate
#interface MyCustomView : UIView {
id<MyCustomViewDelegate> cvDelegate; }
#property(nonatomic, assign) id<MyCustomViewDelegate> cvDelegate;
#protocol MyCustomViewDelegate #optional
-(void)didClickInCustomView:(MyCustomViewDelegate*)view withData:(NSObject*)data;
#end
MyCustomView.m
- (void)myButtonClick:(id)sender
{
[self.cvDelegate didClickInCustomView:self withData:someData];
}
So now you can handle this event in any place where is your custom
view.
Add the button from the interface builder or from the view controller's viewDidLoad using code:
CGRect frame = CGRectMake(0, 0, 24, 24);
UIButton *button = [[UIButton alloc] initWithFrame:frame];
[button addTarget:self action:#selector(handleMyButton:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
Then implement -(void)handleMyButton:(id)sender {}; in your view controller. Or you could instead write -(IBAction)handleMyButton:(id)sender {}; and link method and button using the interface builder.
Then inside the method just paste the block of code you posted above. If you started with the Xcode navigation controller template project it should work.
I think it's cleaner to hide the designated initializer initWithNibName: because it is an implementation detail.
When you say you are subclassing the UIView I don't know exactly what you mean. If you want to add another view controller with a custom view just use the UIViewController template and customize the XIB file, no need to subclass an UIView unless you are really modifying its behaviour, which I guess you are not. The view is a view, and the controller stuff like handling buttons should be in the controller.
The actual controller need to be in the navigation controller stack to be able to push another controller.
Or you can make a new navigation controller instance and push your LocationViewController.

What is the proper way to access navbar buttons of a modal view?

I created a view in IB with a navbar and a table. On the navbar I put two buttons, cancel and done. I use this view like a modal view with:
[self presentModalViewController:controller animated:YES];
My problem is when I use:
[self.navigationItem.rightBarButtonItem setEnabled:YES];
to enable the right button. It doesn't work.
Have I to set a delegate? what code passages I have to do? It works if I create an IBOutlet for the right button and I use [doneButton setEnabled:YES], but I think this isn't the proper way.
In order to place a navigation bar on your modal view controller in interface builder (and set up bar button items that call actions in your detail view controller), you need to go through a level of indirection (your navigation bar will be in one .xib, and the details of your detail view will be in a different xib):
create a xib file containing a navigation controller object, and set its root view controller to be your detail view controller that you want to display modally with a navigation bar.
add bar button items to the detail controller's navigation bar and hook them up to IBActions in your detail view controller object.
your detail view controller will need to be in a separate .xib file
create a "loader" object that just exists to hold the navigation controller iboutlet, and set it to be the File's Owner object of that xib:
#interface Loader : NSObject
#property (nonatomic, retain) IBOutlet UINavigationController *navVC;
#end
#implementation Loader
#synthesize navVC;
- (void) dealloc
{
[navVC release];
[super dealloc];
}
#end
Your xib file containing the navigation controller will look like this:
Make sure the navigation controller object is conntected to the "Loader" object's navVC outlet, and make sure the bar button items are connected to your detail view controller's desired IBActions.
Then you present this whole thing using this code:
Loader *loader = [[[Loader alloc] init] autorelease];
[[NSBundle mainBundle] loadNibNamed:#"ModalVC" owner:loader options:nil];
[self presentModalViewController:loader.navVC animated:YES];
Delegate has nothing to do with your issue.
You probably did put navigation bar into your view directly. Thus things like self.navigationItem doesn't work. You have two choices ...
Connect your buttons to outlets in your code and access them directly.
Or remove navigation bar from your view and present your view controller in this way ...
MyViewController *vc = [[MyViewController alloc] initWith...];
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:vc];
[vc release];
[self presentModalViewController:navCon animated:YES];
[navCon release];
... and now you can access left/right buttons via navigationItem.

Back Navigation Button Not Showing up in Pushed View Controller

I have a problem where I can successfully push a new view controller from my root view controller (using the Navigation-based application using Core Data template), but the detail view, which is a separate xib file from the root view controller, doesn't display the back navigation button. I'm certain that I've done all of the proper connections in IB, and everything else is working as expected.
RootViewController.h
#class ItemsViewController;
#interface RootViewController : UITableViewController <NSFetchedResultsControllerDelegate> {
IBOutlet ItemsViewController *itemsVC;
}
#property (nonatomic, retain) IBOutlet ItemsViewController *itemsVC;
#end
RootViewController.m
#import "ItemsViewController.h"
#synthesize itemsVC;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Pushes to ItemsViewController
ItemsViewController *itemsViewController = [[ItemsViewController alloc] initWithNibName:#"ItemsViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:itemsViewController animated:YES];
[itemsViewController release];
}
ItemsViewController is a UITableViewController subclass with its own xib, also named ItemsViewController. Again, it gets pushed from RootViewController, but the back button doesn't show up like this. I was under the impression that it was a "free" feature of using a navigation controller. As you might expect, this is a very frustrating roadblock and I would appreciate any help.
Does your ItemsViewController class set its title property in its viewDidLoad method?
You should call [tableView deselectRowAtIndexPath:indexPath animated:YES] as the last line of tableView:didSelectRowAtIndexPath: to conform to Apple's human interface guidelines.
Yeah, make sure you have a title on your RootViewController, if it doesn't no button will appear. To set a title programmatically;
self.navigationItem.title = #"Hello Der";
Some extra info for future readers: I ran into this problem while trying to re-populate a nav hierarchy on application load, so I was calling setViewControllers: from the app delegate's application:didFinishLaunchingWithOptions: method. And I was running into the same problem with the back button not showing up.
In my case, setting the root view controller's nav item title in its viewDidLoad did not work, presumably because the other views were loading first and seeing the root's title as null. Instead, I set the root view controller's nav item title in my application:didFinishLaunchingWithOptions: method right before I called setViewControllers:, and that fixed it.
This can also happen if you're a bonehead and mistakenly initialize the view as modal instead of pushing a view to the navigation controller.
oops: [self presentModalViewController:navController animated:YES];
meant: [self.navigationController pushViewController:controller animated:YES];
Try following snippet. It work for me!
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"Image.png"] forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar bringSubviewToFront:[self.navigationController.navigationItem backBarButtonItem]];

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.