Subclassing UIViewController, viewDidLoad called repeatedly - objective-c

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.

Related

Interface Builder: How to load view from nib file

I have a MyCustomView subclassed from NSView designed in a .xib.
I would like to insert this view into some of my other xib's round my application. How should I do this? If i drag a custom view and change the class to MyCustomView, but that does not load my xib-file. Can this only be done programmatically or is there a way to do this inside interface builder?
EDIT1:
Here is a very small demo-project:
http://s000.tinyupload.com/index.php?file_id=09538344018446482999
It contains the default MainMenu xib and my CustomView xib. I would like my CustomView.xib to be displayed inside the custom view added to my MainMenu.xib -- using as less code as possible.
For loading the view you need to add on your window:-
Created custom class of view inheriting to NSViewController
#import <Cocoa/Cocoa.h>
#interface NewViewController : NSViewController
#end
#import "NewViewController.h"
#implementation NewViewController
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
#end
Your xib name is yourview.xib
- (void)windowDidLoad {
NSViewController *yourVC = [[NewViewController alloc] initWithNibName:#"NewViewController" bundle:nil];
[[[self window] contentView] addSubview:[yourVC view]];
}
Sounds like you need a container view. But I think you will have to use storyboard for it to be doable in interface builder.
Use a view controller as it will handle nib loading for you and provide a place to hook up IBOutlet and IBActions in a reusable way.
In your app delegate or whatever controller create an instance of your view controller.
Ask your view controller to load its view.
Cast the return type to your view class name.
Then keep a reference to your view controller and possibly the view.
Tell whatever view to add your view as a subview.
Add any layout constraints.
( you can build out very generic constraints to add themselves in your view or view controller by overriding viewDidMoveToSuperview or viewDidMoveToWindow when superview or window are not nil. Use the same to remove your constraints. )
Oddly you remove a view by telling it to remove itself from its superview.
I'd advise just doing it programmatically:
Add a View to your main xib/storyboard and set the custom class to your custom view's class
In your xib for your custom view, set the File's Owner class to your custom view's class
Hook up any IBOutlets, etc. as needed
Make a __strong property/ivar for holding a reference to the top level NSView of the xib
Implement initFromFrame in your custom view's class roughly as follows:
#interface CustomView ()
{
__strong NSView *nibView;
}
#end
#implementation CustomView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *nibObjects;
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self topLevelObjects:&nibObjects];
nibView = nibObjects[1];
[self addSubview:nibView];
}
return self;
}
The IBOutlet are connected up immediately after the loadNibNamed call, so you can do further initialization from there.
Another option is to do things purely programmatically:
1. In your custom xib, set the root View's class to your custom class
2. Implement awakeFromNib in your custom class to perform initialization
3. Call loadNibNamed: on your custom xib and programmatically add it to the user interface without interface builder.

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

Subclass of UIViewController shows black screen

I created a Subclass of UIViewController in my Project and linked it to a View which is modal-pushed by the "RootViewController". I made absolutely no changes to the derived class, but when the "SecondView" is pushed it turns black every time. If i link that view to the standard UIViewController class everything works fine?
Since the "SecondViewController" is derived from UIViewController I can only guess that the Problem has to do with the alloc/init function but I have no idea where to start.
I can provide the sample code I have in front of me now if necessary.
This is the derived subclass:
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self; }
- (void)loadView {}
- (void)viewDidLoad {
[super viewDidLoad]; }
- (void)viewDidUnload {
[super viewDidUnload];}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait); }
#end
Header:
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
#end
- (void)loadView
{
// 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, then you must NOT override this method.
}
FYI, the comment is automatically generated too.
Change this:
- (void)loadView
{
// 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, then you must NOT override this method.
}
To this:
- (void)loadView
{
[super loadView];
// 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, then you must NOT override this method.
}
And it will work.
Try this
-(void)loadView
{
UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
[view setBackgroundColor:[UIColor whiteColor]];
UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(10, 50, 100, 100)];
[label setText:#"HAI"];
[view addSubview:label];
self.view = view;
}
If you are trying to get the view to load before it "shows" up during the segue from IB you might want to try something like this in the destination view controller.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view layoutIfNeeded];
}

Can I use segues with designated initializers of view controllers?

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

UITextView member variable is always NIL in my UIViewController [duplicate]

In my nib, I have a UITextView component.
In my UIViewController I have a UITextView member field and I have used IB to make sure that the two are connected (at least I think I did that by dragging from "Files Owner" and dropping it on the UITextView in IB and selecting my member variable).
Now, when I update the text via setText:, the UITextView still remains blank.
When I break into my code, the member variable (called textView) is nil.
So I am guessing I have not used IB correctly. How can I make sure that this variable is connected to the UITextView graphic element?
Here is the code
// My interface looks like
#import <UIKit/UIKit.h>
#interface DetailsController : UIViewController
{
UITextView *textView;
}
#property (nonatomic, retain) IBOutlet UITextView *textView;
#end;
// and the implementation
#import "DetailsController.h"
#implementation DetailsController
#synthesize textView;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
// used pressed the "Done" button
- (IBAction)done
{
[self dismissModalViewControllerAnimated:YES];
}
- (void)setDetails: (NSString *)detail withQuote:(NSString *)quote
{
NSLog(#"%#", textView);
[textView setText:detail];
}
#end
save nib file after connecting outlet, build project and run, it should work if it's connected
I can't see agere you are calling setText, but I think that you try to do it in the initializer. GUI code should not be done before the viewDidLoad in the case of using XIB's or loadView if you make your GUI programatically, or the other viewWill/viewDid... methods.
The reason is because the views are not loaded before they are actually needed, it is called lazy loading. You should Google that for more info.