Can I use segues with designated initializers of view controllers? - objective-c

I am new to storyboards and I have set up a segue from a button to a view controller. This view controller is of a custom subclass SFListViewController, which has a designated initializer initWithList:.
Using the designated initializer is the only way to correctly initialize the view controller. However, when using segues the designated initializer won't be called (obviously).
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Show List"]) {
SFListViewController *listViewController = segue.destinationViewController;
// ????
}
}
How can I make the segue call the designated initializer when performed?

As far as I know this is not possible since the storyboard framework simply calls alloc and init on the class you define in Interfacebuilder. Additionally the segue's destinationViewController attribute is read-only so you couldn't simply replace the existing ViewController either.
The only way to use Storyboarding would probably be to create a wrapper-class that internally instantiates the SFListViewController with the desired attributes and then functions as a proxy object and thus propagates viewDid*** and viewWill***-methods to the wrapped class and also returning the wrapped VC's view in a readonly view property... You get the idea.
Generally there are a number of alternative ways to initialize a UIViewController in such a case:
There is an option to specify "User defined runtime Attributes" which could be used for initialisation.
Override the prepareForSegue: method, like you tried, in your root ViewController and do "post-alloc-init-initialisation" there.
If worst comes worst, you could fall back to an IBAction in order to be able to initialize the ViewController yourself.
I hope this helps.
Edit: I can verify that the Proxy-Approach works since I just came across a similar problem with ABPeoplePickerNavigationController where this approach worked nicely. Since we've set up the thing in our story board please note that you have to use awakeFromNib in order to do initial configuration (instead of some init method).
This is the code for my wrapper class:
#import "PeoplePickerViewControllerWrapper.h"
#implementation PeoplePickerViewControllerWrapper
#synthesize ppvc = _ppvc; // This is the object I'm proxying (The proxyee so to speak)
#synthesize delegate = _delegate;
- (void)awakeFromNib
{
self.ppvc = [[ABPeoplePickerNavigationController alloc] init ];
self.ppvc.peoplePickerDelegate = self;
self.ppvc.addressBook = ABAddressBookCreate();
self.ppvc.displayedProperties = [NSArray arrayWithObject:[NSNumber numberWithInt:kABPersonPhoneProperty]];
}
#pragma mark - View lifecycle
- (void)loadView
{
[super loadView];
[self.ppvc loadView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.ppvc viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.ppvc viewWillAppear:animated];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.ppvc viewDidDisappear:animated];
}
-(UIView *)view{
return self.ppvc.view;
}
- (void)viewDidUnload
{
[super viewDidUnload];
[self.ppvc viewDidUnload];
}

Related

How to Subclass UICollectionViewFlowLayout with Storyboard

I'm using a UICollectionView with Storyboard and trying to subclass the UICollectionViewFlowLayout but it doesn't seem to work.
I've created the subclass CollectionViewFlowLayout :
#import "CollectionViewFlowLayout.h"
#implementation CollectionViewFlowLayout
-(id)init
{
NSLog(#"Init of CollectionViewFlowLayout");
if (!(self = [super init])) return nil;
self.itemSize = CGSizeMake(250, 250);
return self;
}
#end
And in the Storyboard's Identity Inspector I changed the class for the flow layout:
But when I save/build/run, the itemSize is not set at 250 and my NSLog isn't being output.
I've seen in examples such as this that you can set the layout in the collectionView controller, but I sort of assumed that wasn't necessary if you set it in the storyboard.
Objects loaded from the storyboard use initWithCoder:, not init. Move your setup code there instead, or have a common method that is called from each initialiser.

iOS Detecting Whether Tab is Changed

I have tabbed iOS application. I need to know which tab is active and detect when tab is changed. In storyboard I have a tab view controller, which changes the view when you click a tab fine. I created a class TabBarController and it is defined as follows:
Header
#interface TabBarController : UITabBarController <UITabBarControllerDelegate>
#end
Implementation
#import "TabBarController.h"
#implementation TabBarController
// In the initialization section, set the delegate
- (id) init
{
self = [super init];
if (self)
{
self.delegate = self;
}
return self;
}
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
NSLog(#"controller class: %#", NSStringFromClass([viewController class]));
NSLog(#"controller title: %#", viewController.title);
}
#end
However, I couldn't detect tab changes with the code above. What do you think that the problem is?
I haven't linked my tab view to any outlets, but segues to other views. Is this the problem? Then, where should I link my outlet to?
Have you confirmed that your init method is being called? I don't think init is the designated initializer for UITabBarController and may not be called when loading the controller from a nib/storyboard.
If that's the case you may find it easier to set the delegate in your viewDidLoad since that will be called regardless of how the object is initialized or else make sure you set the delegate when -initWithNibName:bundle: or -initWithCoder is used to instantiate the object.
Solution to this is the implementation viewDidLoad as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(#"Tabs showed up!");
self.delegate = self;
}

Subclassing UIViewController, viewDidLoad called repeatedly

I subclassed UIViewController as STViewController and noticed that classes inheriting from STViewController have their viewDidLoad method being called repeatedly. Ultimately crashing the app. STViewController is basically a blank implementation at this point. I am subclassing as shown below:
#import "STViewController.h"
#interface WelcomeViewController : STViewController {
STViewController.h
#import <UIKit/UIKit.h>
#interface STViewController : UIViewController
{
}
#end
STViewController.m
#import "STViewController.h"
#implementation STViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)loadView
{
// Implement loadView to create a view hierarchy programmatically, without using a nib.
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
viewDidLoad() from WelcomeViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// hide the buttons
[[self signUp] setHidden: YES];
[[self logIn] setHidden: YES];
}
You are overriding loadView, but your implementation is empty, and you're not assigning a view. Remove the loadView override.
From UIViewController Class Reference (emphasis mine):
You should never call this method directly. The view controller calls
this method when the view property is requested but is currently nil.
If you create your views manually, you must override this method and
use it to create your views. If you use Interface Builder to create
your views and initialize the view controller—that is, you initialize
the view using the initWithNibName:bundle: method, set the nibName and
nibBundle properties directly, or create both your views and view
controller in Interface Builder—then you must not override this
method.
The default implementation of this method looks for valid nib
information and uses that information to load the associated nib file.
If no nib information is specified, the default implementation creates
a plain UIView object and makes it the main view.
If you override this method in order to create your views manually,
you should do so and assign the root view of your hierarchy to the
view property. (The views you create should be unique instances and
should not be shared with any other view controller object.) Your
custom implementation of this method should not call super.

Within a view controller, is it necessary to call removeFromSuperview on subviews during viewDidUnload?

Many people say during a view controller's viewDidUnload method you must remove subviews by calling the removeFromSuperview method. For example, Three20 does something like this:
- (void)viewDidUnload {
[super viewDidUnload];
... snipped ...
[_tableBannerView removeFromSuperview];
TT_RELEASE_SAFELY(_tableBannerView);
[_tableOverlayView removeFromSuperview];
TT_RELEASE_SAFELY(_tableOverlayView);
... snipped ...
}
I understand the reasoning behind this belief: if you call [self.view addSubview:_aView] in loadView, you should call [_aView removeFromSuperview] in viewDidUnload. The thing is, this doesn't seem necessary. When a view controller's view gets released, its dealloc method automatically releases all of its subviews. My test code shows subviews automatically get released when their superview gets released:
#interface TestView : UIView
#end
#implementation TestView
- (id)retain {
NSLog(#"view retain");
return [super retain];
}
- (void)release {
NSLog(#"view release");
[super release];
}
- (id)init {
NSLog(#"view init");
return (self = [super init]);
}
- (void)dealloc {
NSLog(#"view dealloc");
[super dealloc];
}
#end
#interface TestViewController : UINavigationController
#end
#implementation TestViewController
- (void)loadView {
NSLog(#"viewController loadView");
[super loadView];
[self.view addSubview:[[TestView new] autorelease]];
}
- (void)viewDidUnload {
NSLog(#"viewController viewDidUnload");
[super viewDidUnload];
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(#"viewDidAppear");
[super viewDidAppear:animated];
[self dismissModalViewControllerAnimated:YES];
}
- (void)dealloc {
NSLog(#"viewController dealloc");
[super dealloc];
}
#end
The above code produces the following output:
viewController loadView
view init
view retain
view release
viewDidAppear
viewController dealloc
view release
view dealloc
As you can see, when the view controller's main view gets released, its subviews also get released.
Also, the iOS Developer Library [states](http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/BasicViewControllers/BasicViewControllers.html#//apple_ref/doc/uid/TP40007457-CH101-SW4
): "In the case of a low-memory condition, the default UIViewController behavior is to release the view object stored in the view property if that view is not currently being used." Also: "If you use a declared property to store a reference to your view, and that property uses retain semantics, assigning a nil value to it is enough to release the view."
So, if releasing a view automatically releases its subview, is it really necessary to call removeFromSuperview during viewDidUnload?
No, it isn't necessary, the dealloc, as you quite rightly said, will do that for you :) (long post, short answer).
I found it to be necessary in my project. My viewController has a main view (as they all do) and in this case it is defined using a xib (not programmatically allocated and added as a subview). This view has subviews with IBOutlets in the view controller. If, on viewDidUnload, I simply set the IBOutlet properties to nil ( self.mySubView = nil ), then dealloc on that subview is not called. If I first remove it from it's superview (the main view), then dealloc is called.

Bringing up new view controllers - releasing query

I've written some code where I bring up a new view (from my main view controller); then it calls the main controller when it is closed, like so -
-(void)showMyNewView {
MyNewViewController *myNewViewController = [[MyNewViewController alloc] initWithNibName:#"MyNewViewController" delegate:self];
[self.view addSubview:myNewViewController.view];
}
and then when the new one closes, it calls -
-(void)myNewViewControllerDidFinish:(MyNewViewController *)myNewViewController {
[myNewViewController.view removeFromSuperview];
[myNewViewController release];
}
Now this works fine, and there are no leaks, but the compiler moans with warnings about "Potential leak of an object allocated on line x and stored into myNewViewController".
I've been looking at Apple's presentModalViewController:animated: code, which also doesn't release the new modal view controller in the method which creates it, it seems to release it with a dismissModalViewControllerAnimated: call when the delegate's viewControllerDidFinish: method is called. Is there something I'm missing here? Using the presentModalViewController code doesn't generate any warnings. Many thanks for any help.
I think I've figured it out now, and I've written a small bit of code which gives me my own version of "presentModalViewController:animated:" with all the control I want. I'd be grateful to hear what more seasoned coders make of this (it's probably really straight forward but I've not been doing this for very long...), and if there are any problems with the code, etc -
Interface:
#import <UIKit/UIKit.h>
enum {
MyViewLoaderTransitionTypeNone = 0,
MyViewLoaderTransitionTypeSomeEffect,
MyViewLoaderTransitionTypeSomeOtherEffect
};
typedef NSInteger MyViewLoaderTransitionType;
#interface MyViewLoader : UIViewController {
UIViewController *myLoadedViewController;
}
#property (nonatomic, retain) UIViewController *myLoadedViewController;
-(void)loadView:(UIViewController *)theViewController withTransition:(MyViewLoaderTransitionType)theTransition;
-(void)dismissViewWithTransition:(MyViewLoaderTransitionType)theTransition;
#end
Implementation:
#import "MyViewLoader.h"
#implementation MyViewLoader
#synthesize myLoadedViewController;
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
;
}
return self;
}
-(void)dealloc {
[myLoadedViewController release];
[super dealloc];
}
-(void)loadView:(UIViewController *)theViewController withTransition:(MyViewLoaderTransitionType)theTransition {
[self setLoadedViewController:theViewController];
UIView *theLoadedView = theViewController.view;
[self.view addSubview:theLoadedView];
// do all sorts of transition stuff here
[theViewController viewWillAppear:NO];
}
-(void)dismissViewWithTransition:(MyViewLoaderTransitionType)theTransition {
UIView *theLoadedView = self.loadedViewController.view;
// do all sorts of transition stuff here
[theLoadedView removeFromSuperview];
self.loadedViewController = nil
}
I just use MyViewLoader as the superclass of any view controllers where I need it.
Thanks for any comments / help!
The usual thing to do here is, when you add a subview to a view, release the subview directly after. The parent view becomes responsible for the subview. When removeFromSuperview is called later, that decrements the retain count and the subview is automatically released.