the ViewController in MVC, in XCode - iphone

a quick question,
I have been dabbling with XCode off late and am trying to understand the View Controller, while I get the nitty gritty of it, one thing I fail to see is where the View Controller class object is instantiated. It is, in essence a class and hence has to have an object instantiated to be able to send messages to it.
It's kind of left me scratching my head.
Thanks much!

It is instantiated whenever it needs to be displayed. By you.
For example, if you wanted to display a new view on the navigation stack on the press of a button?
-(IBAction)buttonClicked:(id)sender{
/* Create VC here */
YourViewController *controller = [[YourViewController alloc]initWithNibName:#"ViewName"];
/* Push */
[self.navigationController pushViewController:controller animated:YES];
/* Let go since you don't have control over it anymore. */
[controller release];
}
I believe it is generally better to do this instead of holding an instance in memory in most situations, to prevent too much memory usage.
Now (assuming iOS5 is out of NDA now that most of it has been announced today), you can use Storyboarding in XCode that will handle all this for you.

The view controller is instantiated in it's init function designated initializer being the following:
-(id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
{
if( (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) )
{
// Custom initialization
}
return self;
}
As you will perhaps have noted in some of Apples examples or any other source code you've looked through you might have seen a line of code similar to
MyViewController* viewController = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
This is where/when the view controller gets instantiated. You'll notice that a view controller has a member object of type UIView called view it is this that gets added to the window or view that this view is to be apart of.
The view controller is created to handle messages pertaining to this view. It's all spelled out here.

Related

pushViewController, when to set UILabel text, and does setNeedsDisplay need to be called?

I ran into something odd today that maybe someone knows something about. I have a subclass of UIViewController and its associated NIB. I set the labels in the UIViewController methods and all that works fine.
Now from another class, I create that ViewController again because I want to reuse it. I do this:
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
vc.titleLabel.text = #"testing";
vc.myTextLabel.text = #"yo";
self.navigationController pushViewController:vc animated:NO];
[vc release];
This does NOT work. I have no idea why this does not work. I would think I would set all the labels, then show the view controller by pushing it onto the stack.
However, if I do this:
[vc.view setNeedsDisplay]; // why here???
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
vc.titleLabel.text = #"testing";
vc.myTextLabel.text = #"yo";
self.navigationController pushViewController:vc animated:NO];
[vc release];
This DOES work. This does not make sense to me. I thought setNeeds Display was called AFTER a view needs to be redrawn. If I move setNeedsDisplay to the end of the block it does NOT work. It only works at the beginning of the block which is very odd to me. Any one encounter this before or know why it works this way? Thanks.
The reason is that a view controller's view is lazily-loaded. This means the controller's view is only loaded from a nib (or via -loadView) when you access the view property for the first time. If you attempt to access the labels before the view has been loaded, they will be nil and any messages you send to them will be no-ops.
So to force the view to load, you can do this:
/* make sure the view is loaded */
[vc view];
/* Access the label properties */
vc.titleLabel.text = #"testing";
However, forcing the view to load may not be a good idea in all situations, especially if the view controller is not going to be displayed immediately and you want to save memory.
In this case you can create the labels in the controller's init method so they always exist, and add them to the view controller's view manually in -viewDidLoad, rather than in your nib. This will allow the standard lazy-loading behaviour to work, but users of your class can still set properties on the labels before the view is loaded.
An alternative is to expose simple NSString properties with associated ivars on the view controller to represent any titles or text in the view. Then in your -viewDidLoad you can set the text of the labels to the value of these properties. Users of your view controller can then set these properties before the view has loaded.

A couple of problems customizing Apples MultipleDetailViews Example

I have been trying to add/implement this example to my existing Split View app tests.
Apple Example
I what to use the concept of replacing the detail view or right view, otherwise my app will be different. It is this difference that is causing my problems.
I have a rootviewcontroller or left view and upon choosing something here a new view is pushed onto this view. Upon choosing something in this "pushed view" I want to change the detail view or right hand view. This is the difference to apples example where the rootview does not have a pushed view on it and thus references are not broken.
Below is my change code - the new View DVCases is being initialized but the didload is not happening.
The issues are learner issues to do with my classes.
This below code is in my RootViewController implementation code but my reference to splitviewcontroller is not working if there is a new view pushed.
Second self.navigationcontroller is not correct because I have pushed a second view to the rootviewcontroller.
To centralize and simplify the code what I have done is from the delegate of the pushed view in the didselect event i call a method found in the rootviewcontroller passing the index as a parameter. The code for my custom method contains what is below.
So my question is how do I do this in my situation where I have pushed other views onto the rootview or left side. It appears that after pushing a view the reference to splitviewcontroller is gone and self.navigationcontroller is also gone/or wrong.
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (value == 0) {
DVCases *newDetailViewController = [[DVCases alloc] initWithNibName:#"DVCases" bundle:nil];
detailViewController = newDetailViewController;
}
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
[viewControllers release];
// Dismiss the popover if it's present.
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
// Configure the new view controller's popover button (after the view has been displayed and its toolbar/navigation bar has been created).
if (rootPopoverButtonItem != nil) {
[detailViewController showRootPopoverButtonItem:self.rootPopoverButtonItem];
}
[detailViewController release];
I would appreciate any tips or help you might have.
Initialization of any viewcontroller class does not mean that it will make call to viewDidLoad method.
viewDidLoad method will only be called when you load view of that viewController. Generally we do it either by following methods.
1. Pushing it on navigation stack.
2. Presenting it using modal transition.
3. Adding it on some other view using [someView addSubView:controller.view];
4. Selecting any tabBar item for the first time Or tapping tabBar Item twice.
there may be some other scenarios.
But right now in your code I don't see any of this element.
Initialization means you are calling the direct method for intialization(calling its constructor) like here in above code initWithNibName will call this method of DVClass not any other(until this method had call for other methods inside it).
Thanks
As I am learning to properly code - my problems centres around that.
The above code is perfect as long as you call it using the same instance. I was not. Thus it was not working.
In the end I made my RootViewController a delegate for a method that has the above code. Thus when in another view - this view can call this method and the proper or real instance of RootViewController will implement it.

How do I pass an object into a delegate?

I am creating a delegate view controller and presenting it to the user to perform an action but I would like to change a NSString on the delegate view controller based on the originating view controller. For example if the delegate view controller is a delegate of viewControllerA, then display Foo, but if its a delegate of viewControllerB then display Blah. ALthough I cant figure out how to pass some sort of information that indicates what the originating view controller is. I noticed that if i do an NSLog(#"I'm from %#",[self delegate]); it will tell me what the originating view controller is, as well as the memory address, but I cant seem to translate that into an NSString object to examine its value. If theres a way to make that work, or a better way to do this then that works too...
- (IBAction)editDate {
DatePickerViewController *datePickerView = [[DatePickerViewController alloc] init];
datePickerView.delegate = self;
datePickerView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:datePickerView animated:YES];
[datePickerView release];
}
It seems like you're using some terminology in ways that are different from what most Objective-C coders would mean.
Here you're instantiating a view controller to show as a modal view. That view controller has a property called delegate that allows it to call some methods to report changes to its state. That doesn't make it a "delegate view controller", that makes it "an object with a delegate".
You happen to be using another view controller class as the delegate, but any object that implements the methods that DatePickerViewController objects want to call to report changes could be assigned to that delegate property.
I think that the question you're asking is "how do I make the DatePickerViewController display different information depending on what kind of object it's reporting to?", and the answer is much the same as "how do I make a UILabel show different text depending on the view controller that created it?"—you set properties or call methods on in when you create it.
If you really just want to pass a string to DatePickerViewController, you could add an NSString* property to DatePickerViewController and set it with arbitrary text, with
datePickerView.myString = #"some information that you want";
You could use the class of the delegate.
if([[self delegate] isKindOfClass:[ViewControllerA class]]) {
[self doViewControllerAThings];
}
else {
...
}

ipad - dismissing a UIPopoverController

I have a button inside the content of a UIPopoverController. This button runs a method called myAction.
MyAction has the form
- (void) myAction:(id)sender
so, myAction receives the id of the caller button.
Now, inside this method I would like to dismiss the UIPopoverController, but the only thing I have is the ID of the caller button. Remember that the button is inside the UIPopoverController.
Is there a way to discover the ID of the UIPopoverController, given the button ID I already have?
thanks.
Unfortunately no. At least, not within the standard practices. You might be able to travel up the responder stack to find it, but it's a hack, it's buggy, and it's really, really messy.
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover. Usually that would be the owner of the popover (not the controller showed within the popover). When the button is pressed, it can send a message to the owner controller, which can then dismiss the popover.
You might be tempted to have the controller displayed inside of the popover be the owner of its own popover, but coding this way is brittle, can get messy (again), and may result in retain loops so that neither ever gets released.
You can access the presenting popoverController by accessing "popoverController" with KVC.
[[self valueForKey:#"popoverController"] dismissPopoverAnimated:YES]
I have this working, and I do not think it is a hack. I have a standard split view iPad app. I then added a method on my detail controller (the owner of the pop over) to handle the dismissal.
On the standard split view architechture, both the root and detail view controllers are available via the app delegate. So I bound a button click inside the pop over to call a method which gets the app delegate. From there I call the method on the detail controller to dismiss the pop over.
This is the code for the method on the View Controller that is displayed inside the popover:
- (void) exitView: (id)sender {
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.detailViewController exitDrill];
}
Then the simple method to dismiss on the Detail View Controller:
- (void) exitDrill {
if(dtController != nil){
[dtController dismissPopoverAnimated: YES];
[dtController release];
}
}
I like the ability to do this because it give me a way to show a user how they can exit a pop over. This may not be necessary in future versions of the app; for right now, while this paradigm is still new to the platform, I prefer to let the users gexit a display in a couple fo different ways to make sure I minimize frustration.
As Ed Marty already wrote
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover
This is very true; however, when showing a UIPopoverController, the class opening the popovercontroller keeps this resource already. So, what you could do is to use this class as the delegate class for your Popover Controller.
To do so, you could do the following, which I use in my code.
In the class opening the popover, this is my code:
- (void)showInformationForView:(Booking*)booking frame:(CGRect)rect
{
BookingDetailsViewController *bookingView = [[BookingDetailsViewController alloc] initWithStyle:UITableViewStyleGrouped booking:booking];
[bookingView setDelegate:self];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:bookingView];
self.popController = [[UIPopoverController alloc] initWithContentViewController:navController];
[self.popController setDelegate:self];
[self.popController setPopoverContentSize:CGSizeMake(320, 320)];
rect.size.width = 0;
[self.popController presentPopoverFromRect:rect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
}
- (void)dismissPopoverAnimated:(BOOL)animated
{
[self.popController dismissPopoverAnimated:animated];
}
So what I am doing here is creating a UINavigationController and setting a BookingDetailsViewController as its rootViewController. Then I am also adding the current class as delegate to this BookingDetailsViewController.
The second thing I added is a dismissal method called dismissPopoverAnimated:animated.
In my BookingDetailsViewController.h I added the following code:
[...]
#property (nonatomic, strong) id delegate;
[...]
And in my BookingDetailsViewController.m I added this code:
[...]
#synthesize delegate = _delegate;
- (void)viewDidLoad
{
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(closeView)];
[self.navigationItem setRightBarButtonItem:closeButton];
[super viewDidLoad];
}
- (void)closeView
{
if ([self.delegate respondsToSelector:#selector(dismissPopoverAnimated:)]) {
[self.delegate dismissPopoverAnimated:YES];
}
else {
NSLog(#"Cannot close the view, nu such dismiss method");
}
}
[...]
What happens is that when the "Close" button in the UINavigationController is pressed, the method closeView is called. This method check if the delegate responds to dismissPopoverAnimated:animated and if so, it calls it. If it does not respond to this method it will show a log message and do nothing more (so it wont crash).
I have written my code using ARC, hence there is no memory management.
I hope this helped you.

UINavigationController right usage

I've implemented UINavigationController in my iphone application as main navigation interface.
When I need to add some new xib to my UINavigationController stack I have to write new method like this (example for ContactsView.xib):
- (void) switchToContactsView
{
// lazy load
if (self.contactsViewController == nil)
{
ContactsViewController *contactsController = [[ContactsViewController alloc]
initWithNibName:#"ContactsView" bundle:nil];
self.contactsViewController = contactsController;
[contactsController release];
}
[navigationController pushViewController:contactsViewController animated:YES];
}
The problem is, I have lots and lots of .xib screens in my application now now and for every new screen I have to copy and paste the same code, just changing the name of method and class names in it.
Is there some more efficient way of doing this? Like maybe create some single method that accepts parameters (xib and uiviewcontroller subclass name), so I can just call it with those parameters to load new xib and switch to it. The problem is, I don't know how to pass class name to a method (eg ContactsViewController class in the example above). Any help?
- (void) switchToAViewController: (NSString *)vCName: (NSString *)nibName
{
// lazy load
if (self.toBePushedViewController == nil)
{
//Assuming all VCs are subclasses of UIViewController
UIViewController *tempViewController = [[NSClassFromString(vCName) alloc]
initWithNibName:nibName bundle:nil];
self.toBePushedViewController = tempViewController;
[tempViewController release];
}
[navigationController pushViewController:self.toBePushedViewController animated:YES];
}
Also refer to NSClassFromString returns nil
Is there a reason you have many xibs in your project ? Read the apples interface guidelines, Many new-commers to iPhone development misunderstand the rationale behind some of the interface elements.
Aside from that, if you need many xibs, but all of them have exactly the same functions consider linking the xibs to the same class file, so one controller having 3 xibs etc. that way you will only need to point to the correct xib instead of copy the whole method.
Just a thought, but please explain the reason behind having so many xibs, what is your application's purpose?