What's the Best Way to Send Parameters to a UIViewController? - iphone

I ask this question because it appears that viewDidLoad gets called before the main block of the initializer does and the class variables I'm initializing in the initializer are turning up nil in viewDidLoad. For your reference, I'm doing this entire viewcontroller programmatically and I've created a custom initializer so I can take in various parameters to be used in my viewcontroller. My custom initializer calls UIViewControllers designated initializer of course.
Basically, I'm curious about what is the proper design pattern for sending in parameters to a UIViewController? I've read other threads about this and haven't really gotten a definitive answer. Should I just skip the initializer and set the properties manually (from outside the class)? That seems kind of bleh, I'd really like to send in some parameters and am curious how others go about doing this?

The reason viewDidLoad is called before initialization completes, is probably because you call the view method in the initializer. For example:
- (id)init
{
if ((self = [super init])) {
_thing = 123;
_other = self.view.frame.size.width / 2;
}
return self;
}
viewDidLoad is called when the view loads. And the view loads as soon as you call the view method (or access the view property, if you prefer). So you should avoid referencing the view in init.
To answer your question, I prefer to create an init method for my view controllers.
- (id)initWithThing:(MyThing *)thing thang:(MyThang *)thang
{
if ((self = [super init])) {
_thing = [thing retain];
_thang = [thang retain];
// ...
}
return self;
}
You can also use properties to set extra variables after initing. Personally I prefer to do this with optional properties only, though, and put required properties in the init method. That way I can't init the view controller with an invalid state.

I encapsulate the state of my controllers in an additional state object. So the controller usually has ivars for the GUI elements and a reference to this state object.
State objects are handled by a StateManager object on my delegate. So instead having a controller referencing another controller and set variables directly, all changes go through this manager. A little more work but way less messy.
Any class is able to retrieve the state for any other controller and change it. This is the purpose of some controllers (eg: choosing a video from youtube happens on a dedicated controller). But usually is just one class getting a state to push the next controller with initWithState:. The pushed controller then applies the state to its GUI in viewDidLoad or changes this state object passed from the previous class.
The StateManager keeps a dictionary of all the state objects. I keep the state object graph light, eg: I store a reference to a image, but not the image itself. The real beef of the application is in Core Data, I only use all this to handle the GUI state. The StateManager listens for the application resign event and saves the state dictionary to disk using NSCoding. The state object ivars are always nil or some meaningful value, no dangling pointers.
I'm working on my 2nd iPhone app and this seems to be working, but I wonder too how other people does it. Any input is welcome.

Basically you should have properties defined for your input data but add a custom init-function.
If you create a custom init-Method you should be fine - remember that initWithNibName:bundle: is the main initializer of UIViewController so this is what you want to call from your custom init-Method. viewDidLoad will always be called after your init-Method at the first usage of customVC.view (either from your code or via Framework):
- (id)initWithDataObject:(MyDataObject*)obj
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
self.dataObj = obj;
}
return self;
}

Related

2 delegates of one class

I use delegates to run some methods.
I got class webrequests with 2 delegates: menuVC and mapVC.
When I in mapVC webrequests is accomplish delegates methods of mapVC class.
When I in menuVC webrequests is tried to accomplish delegates methods of mapVC class and crashed app.
Method in webrequests do not helps
if ([self.delegate isKindOfClass: [MenuViewController class]])
{
self.delegate = [MapViewController class];
}
Why it happens?
I use in both classes
self.webRequests = [WebRequests sharedInstance];
self.webRequests.delegate = self;
Original answer:
You can only have one delegate of an object at one time, unless the object is designed to allow for different delegate protocols (e.g. UITableView has both a "delegate" AND a "datasource").
You also can not set your delegate to a non-instantiated object like what you are doing on this line:
self.delegate = [MapViewController class];
This needs to be an actual allocated and instantiated object.
Like "self.mapViewController" that you just pushed or created.
For this question, you probably need to better explain what you are ultimately trying to do, since it looks like your current delegate is a MenuViewController object and you're trying to switch the delegate to a MapViewController object.
New answer:
Since you want things to happen in two different view controllers, the best way to do what you want to do is use a "NSNotification" (which allow multiple objects to observe -- or watch -- for things happening).
Here is a tutorial you can look at, to get a good start with it.

Where to initialize custom UIView, instantiated in Interface Builder?

I have a subclass of UIView that's instantiated in a XIB file. I need it to do some initialization (settings some variables and creating a subview).
However, I do not always instantiate this view via Interface Builder. I do it programmatically too. In both cases, the initialization needs to be the same.
My designated initializer is initWithValues:.
The question is; where do I perform the initialization?
Since I have to perform it in 2 different locations, I figured I need to refactor it in a separate initialize method (or something like that), and call it from initWithValues:.
But when loading from IB, both initWithCoder: and awakeFromNib are called. From which method do I have to call initialize? Or do I have to call initWithValues: from initWithCoder: and do nothing in awakeFromNib?
You should use initWithFrame: when initializing views (since it's the designated initializer). Hence, if you have initWithValues: make sure you call initWithFrame: from it.
Something like this should work for initializing: ;)
- (void)initialize{
//init your ivars here
}
- (id)initWithCoder:(NSCoder *)aCoder{
if(self = [super initWithCoder:aCoder]){
[self initialize];
}
return self;
}
- (id)initWithFrame:(CGRect)rect{
if(self = [super initWithFrame:rect]){
[self initialize];
}
return self;
}
I was going to add a further explanation, but mplappert's answer is clear enough. Use awakeFromNib if necessary.
That depends on what you need to initialize. As soon as awakeFromNib gets called, all outlets and action connections of your view are established which is not the case in initWithCoder:. So if you need to rely on those connections, use awakeFromNib. Otherwise you can safely do all your initializing in initWithCoder:.
Unfortunately, the above answers don't take into account these things:
- (void) awakeAfterUsingCoder - and the fact it's called after anything is created by the coder (once for every Xib view).
awakeFromNib suffers from the same fate, I've noticed. (The reason I found this)
Another initializing issue is that initWithCoder and initWithFrame can be avoided for custom views. And if they are called, lazy loading (though not as important on views themselves), means you "might" be able to modify values. I believe I've done so in initWithCoder, but if you then initialize values in awakeFromNib, it's undone at least once.
I've gone so far as to:
- (void) awakeFromNib (or didMoveToSuperView);
{
BOOL called = NO;
if(!called)
{
called = YES;
}
}
Another method I use is to simply call the initializer needed, then call my own class or superclass-specific initializer.
I, too, am looking for a dependable one-time place I can rely on. Until then, I hope my headaches save the next person an hour or so.
Steve

addObserver questions, try to check if data is loaded

I have a ViewController that initializes another class that loads data into a mutable array and saves it as a property on itself.
here is my ViewController init code:
-(id) initWithCollectionID:(NSString *)aCollectionID {
if (self = [super init]){
collectionID=aCollectionID;
dataSource = [[CollectionListDataSource alloc] initWithCollectionID:collectionID];
}
return self;
}
once dataSource has loaded all of the data into it's property dataSource.collectionItems I set dataSource.loaded = #"true";
how do I use addObserver to watch that value and fire off a function in my ViewController?
something like this I'd assume:
[self addObserver:dataSource forKeyPath:#"loaded" options:NSKeyValueChangeNewKey context:nil];
Then what do I do?
As your code stands now, it will pause until the data is loaded regardless of whether you use notifications or not. It will not progress past:
dataSource = [[CollectionListDataSource alloc] initWithCollectionID:collectionID];
...until the CollectionListDataSource object has completed its own initialization (which I presume also means the loading its data) and returns an instance of itself.
If you want the CollectionListDataSource object to load while the view controller keeps on initializing, you will need to put the CollectionListDataSource object on another thread. However, you can't have an attribute object running on separate thread.
You seldom need to jump through such hoops. Unless this array is very large (10k+ objects) you most likely don't have to worry about. In most cases, you need the data before the view can function anyway so there's no point in letting the view go on without the data.
If you do need actually need to observe an attribute of another object, see Key-Value Programming Guide: Registering For Key-Value Observing for details.

Creating an object to act as a delegate - Objective C

My question is simple actually, how do I create an object to act as a delegate, instead of including the delegate methods in my view?
For example, I have x functionality that requires delegate methods, and they're currently setup to use self as the delegate. I'd like to put those methods in their own object so that the delegate methods can be called and do stuff if the view has ended.
What's the best way?
for example, NSXMLParser delegate methods - they exist, the delegate is defined, but I dont want to call them as self in my view controller... what other option do I have?
You can specify another custom class to handle the delegate methods, if you wish. Simply create a class, call it MyXMLParserDelegate or something similar. Then, all you have to do is tell your NSXMLParser object that it should use an instance of your class as its delegate.
If you are using Interface Builder, add a new object to the XIB file, set its class to MyXMLParserDelegate, and then drag a connection from your NSXMLParser object's delegate selector to the new object.
If you are doing it programmatically, the basic operation looks like this:
MyXMLParserDelegate * myDelegate = [[MyXMLParserDelegate alloc] init];
[someXMLParser setDelegate:myDelegate];
Keep in mind, however, that delegates are not retained, so in order to do this without leaking memory, you should add an ivar of type MyXMLParserDelegate to your viewController class, and then do the following:
// in your #interface block:
{
...
MyXMLParserDelegate * myDelegate;
}
// in your init method:
myDelegate = [[MyXMLParserDelegate alloc] init];
// in your awakeFromNib method (or anywhere else it seems appropriate):
[someXMLParser setDelegate:myDelegate];
// in your dealloc method:
[myDelegate release];
Check out this answer, I think it covers what you need: How to use custom delegates in Objective-C

Whats the best place to set a View Controller ivar?

I have an XML reader class which I initialize with a URL
- (id)initWithURL:(NSURL *)url
This class adds objects to an array in the calling class using an instance variable
// in the interface
ViewController *viewController;
// in the implementation
[viewController addObject:theObject];
Now, I initialize my XML reader class, then set the View Controller separately:
XMLController *xmlController = [[XMLController alloc]
initWithURL:url];
xmlController.viewController = self;
My question is whether I should create a new init function which sets the viewController at the same time.
Thanks.
Edit: I forgot to add that my XML reader starts downloading and parsing the class in the init function.
It's entirely up to you. You can see examples of both styles all over Apple's code.
As long as you don't make any assumption about the viewController property's value being constant over time, it should be fine to leave it as-is.
BTW, you might think about refactoring the addObject: logic into a protocol instead of requiring a specific subclass. Something like:
-xmlController:didDecodeObject:
Or whatever makes sense for your XMLController object's logic.
If your init routine is going to cause delegate/controller calls, or set off asyncronous activities (potentially including your downloading) that could message the delegate, then you should include it in the init function.
Otherwise your controller might miss potential delegate messages such as xmlController:didStartConnection that might be called before your initWithURL routine returns.
Also, if the controller/delegate is a required part of the XMLController activities, then you should include it in your init routine.
So yes, in this case I would suggest:
XMLController *xmlController = [[XMLController alloc] initWithURL:url andController:self];