I've read a bunch of answers on SO already, but I'm a little confused.
I have a tab bar controller subclass I created, and in its viewDidLoad, I'm creating each of the view controllers. However, I have a dependency that I'm passing into the parent, and in turn into the view controller for each tab. I'm passing that dependency in with a custom init method (NS_DESIGNATED_INITIALIZER declared for it in the header). However, it looks like [super init] triggers viewDidLoad directly, so the dependency isn't set properly when the other view controllers are created.
Here's my custom init method:
- (instancetype)initWithSession:(T2Session *)session
{
self = [super init];
if (self) {
_session = session;
}
return self;
}
I'd like session to be set by the time I create the view controllers, but I'm sort of confused about what the best way to do this is. Any advice is much appreciated. Thanks in advance!
I've come across this situation before.
You probably sat there (like me) wishing viewDidLoad didn't get called so soon.
Anyway, this is what I settled on:
- (instancetype)initWithSession:(T2Session *)session {
if (self = [super init]) {
self.session = session;
}
return self;
}
- (void)setSession:(T2Session *)session {
_session = session;
... call the setup methods here, instead of viewDidLoad
}
At first I thought this breaks the golden rule of not calling self.xxxx from within an initializer.
However, I think that rule is only really relevant when calling methods on IBOutlets that may not have been wired up yet.
In this case, T2Session *session isn't a nib outlet.
Alternatively, if you prefer not to break that rule you could always remove the custom initializer .. and revert to using regular property-injection rather than constructor-injection. e.g.
T2Session *session = .....
MYTabBarController *tabBarController = [[MYTabBarController alloc] init];
[tabBarController setSession:session];
These are just my thoughts, hope it helps.
Related
I would like to know which is the best way to send parameters between viewControllers. I know there are two possibilities, pass the parameters in the public properties after the init call.
ViewController *vc = [ViewController alloc] init];
vc.propertyOne = #"whatever";
vc.propertyTwo = #"whatever2";
Or creating a new Custom init like
initWithProperty:(NSString *)prperty1 andPropertyTwo:(NSString *)property2
{
self = [super init];
if (self) {
self.propertyOne = prperty1;
self.propertyTwo = property2;
}
return self;
}
ViewController *vc = [[ViewController alloc] initWithProperty:#"whatever andPropertyTwo:#"xxxx"];
I would like to know Advantages and disadvantages of each one, and "when" and "why" is better use one of them.
You should pass parameters to a custom init method if those values are needed immediately in the implementation of the init method.
You should use properties, that are set just after calling alloc/init, if you have several properties to set and those properties aren't needed in the init method itself.
Many times the properties aren't needed until viewDidLoad (in the case of a view controller) so using properties is cleaner. You don't want to end up with an init method with a dozen parameters.
I clear out the table view delegate and data source methods directly in dealloc as below:
- (void)dealloc
{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
But looking at some online examples of dealloc, I see that everybody is checking whether the view is loaded before clearing out the delegate and data source like below:
- (void)dealloc
{
if ([self isViewLoaded])
{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
}
Curious to know is it just to check if the memory is allocated to the view, if yes then clear else not. Or is there any specific reason for adding a check here?
If your controller is a table view controller then calling self.tableView when the view isn't loaded will cause it to load. If you're about to get deallocated then there is no point going to the effort of loading the view. So checking isViewLoaded is a cheap way of preventing that from happening.
What #Wain mentions is right. However, as per the iOS Memory Management Guidelines you should
NEVER use self to refer to an ivar inside init or dealloc precisely for situations like the one he described.
The correct way to do it would be:
- (void)dealloc
{
_tableView.delegate = nil;
_tableView.dataSource = nil;
}
Hope this helps!
I have a uitextfield subclass and in the init method and setDelegate I have this:
- (void) setDelegate:(id<UITextFieldDelegate>)paramDelegate{
[super setDelegate:paramDelegate];
MRAAdvancedTextFieldDelegate *limitedDelegate = [[MRAAdvancedTextFieldDelegate alloc] init];
self.delegate = limitedDelegate;
}
I am using ARC, but this results in a BAD_ACCESS. Any ideas?
You write self.delegate = limitedDelgate within your setDelegate: method. This is exactly the same as calling [self setDelegate:limiatedDelegate]. Since you are within the -setDelegate: method itself, you are causing infitine recursion. Hope this helps!
EDIT: per your comment about your intention, override it like this:
- (void) setDelegate:(id<UITextFieldDelegate>)paramDelegate{
MRAAdvancedTextFieldDelegate *limitedDelegate = [[MRAAdvancedTextFieldDelegate alloc] init];
[super setDelegate:limitedDelegate];
}
But I don't believe it's a good idea to do this - you should have your client code pass in instances of your delegate instead.
self.delegate = limitedDelegate;
is turned into
[self setDelegate:limitedDelegate];
by the compiler, resulting in an infinite loop. Solution: instead of using the property, use the instance variable instead in the custom setter method:
delegate = limitedDelegate;
I'm trying to tweak the behavior of the default IASKAppSettingsViewController so it can provide custom subviewcontrollers (as for type = PSChildPaneSpecifier) for custom views (type = IASKCustomViewSpecifier).
I've tried to subclass IASKAppSettingsViewController without adding any new functionality to the code.
However when I load up my own subclass the settings view is completely empty - even for a bare subclass. When I switch back to the default IASKAppSettingsViewController everything works again as expected.
I've tried setting breakpoints various places in my subclass and everything seems to be working fine - except that nothing is displayed.
I've tried looking for clues in the documentation, but can't seem to find anything that explains why. The documentation for InAppSettingsKit even states that it's designed for subclassing.
What am I missing here? Any clues are appreciated - or even better a small step-by-step guide for dummies.
Updated with code:
Setting up the flipside view. It works with the standard IASKAppSettingsViewController but fails with my empty derived MyCustomSettingsViewController
- (IBAction)showInfo:(id)sender
{
// Setup navigation controller for settings view
MyCustomSettingsViewController *settings = [[MyCustomSettingsViewController alloc] init];
//IASKAppSettingsViewController *settings = [[IASKAppSettingsViewController alloc] init];
settings.delegate = self;
settings.showDoneButton = YES;
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:settings];
navigation.navigationBar.barStyle = UIBarStyleBlack;
[settings release];
// Display the settings
navigation.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:navigation animated:YES];
[navigation release];
}
Try to add this method to your subclass:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
return [super initWithNibName:#"IASKAppSettingsView" bundle:nibBundleOrNil];
}
If you don't do that, the default implementation of IASKAppSettingsViewController kicks in and it tries to find a nib named after your class name, in this case "MyCustomSettingsViewController.nib". This probably doesn't exist.
This happens because the default implementation of -init calls -initWithNibName:nil bundle:nil and nil as the filename means to search for a default nib.
Try setting breakpoint on initialization steps and if you are adding that as a subview/ pushing on navigation controller. Use NSLogs for each method so you can find the issue. If nothing works send me the code (create a demo code using IASKAppSettingsViewController) to recerate the problem then I'll look into the issue.
Very interesting problem when using loadView in UIViewController.
Usually we used like this way
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
NSLog(#"loadview");
[super loadView];
}
If remove
[super loadView];
We will get dead loop with this
- (void)loadView {
NSLog(#"loadview");
}
Why ?
Only one way to make infinite loop in this case - is getting view property until its not set. If you write next (for example):
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:self.view.bounds];
}
You'll got infinite loop, but
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectZero];
}
works OK.
So you can't to access view property until you didn't set it.
Since you are just INHERITING what's being implemented in super class(UIViewController), if you don't call super methods, then implementation that needs to be done is NOT done.
Almost all super methods do something critical and the local class inheriting super class implementations must either override them all together (unless you know everything about what super does by referring to the documentation, it's never a good idea), or just ADD local class implementation to the inherited super class implementations.
In conclusion, whenever you inherit a class, which is in most cases of software development, you should let the super class do its implementations unless it's safe to override them.
If I am correct, it seems like super loadView implements something very critical to avoid the loop.
ADDITIONAL NOTE:
However, based on the documentation, you should not call super method: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
Probably, the reason for infinite loop is caused by not implementing view property appropriately.
If you override loadView, you are expected to provide a root view for the controller's view hierarchy. If you don't provide it loadView would get called every time the view is referenced, possibly leading to an infinite loop. From the docs:
If you specify the views manually, you must implement the loadView method and use it to assign a root view object to the view property.
Implementations that would cause an infinite loop:
- (void)loadView {
NSLog(#"loadview");
}
...self.view is nil after loadView
- (void)loadView {
self.view; // Or anything that references self.view
}
...referencing self.view causes loadView to be invoked, hence an infinite loop.
Correct:
- (void)loadView {
self.view = [[UIView alloc] init];
if (self.view == nil) {
[super loadView]; // Try to load from NIB and fail properly, also avoiding inf. loop.
}
}