initWithNibName vs init - unexpected behaviour - ios

I have noticed that when adding a viewController with interface builder and a nib,
That I don't have to call initWithNibName for it to pick up the associated nib, I can just call init!
Any idea why?
ie.
This:
NotificationManagementController *notificationView = [[NotificationManagementController alloc] initWithNibName:#"NotificationManagementController" bundle:nil andCurrentNotifications:nil];
and This:
NotificationManagementController *notificationView = [[NotificationManagementController alloc] init];
Both seem interchangeable....
Thus if I then call these line of code:
notificationView.delegate = self;
notificationView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:notificationView animated:YES completion:NULL];
I see all changes in the nib.

NotificationManagementController *notificationView = [[NotificationManagementController alloc] initWithNibName:#"NotificationManagementController" bundle:nil andCurrentNotifications:nil];
is simply not neccessary, and even frowned upon by some (including me).
NotificationManagementController *notificationView = [[NotificationManagementController alloc] init];
is much cleaner (and safer) in that is hides implementation details, but will effectively call initWithNibName: behind the scenes.
I like to think of it this way:
- (id)init
{
self = [[NotificationManagementController alloc] initWithNibName:#"NotificationManagementController" bundle:nil andCurrentNotifications:nil];
if (self)
{
// Initialization
}
return self;
}

Related

Access view controller without re-initializing

Essentially I'm working with 3 view controllers.
Main view which starts a download. (Webview based which passes the download).
Modal download controller. (Tab based).
Downloader (HCDownload).
In the main view my download gets passed like so:
//Fire download
[activeDL downloadURL:fileURL userInfo:nil];
[self presentViewController:vc animated:YES completion:nil];
activeDL is initialized in viewDidLoad:
activeDL = [[HCDownloadViewController alloc] init];
If I removed the presentViewController, it still downloads, which is fine. Then i tap my Downloads button, it brings up the controller which defines the tabs like so:
center = [[CenterViewController alloc] init];
activeDL = [[HCDownloadViewController alloc] init];
completedDL = [[DownloadsViewController alloc] init];
activeDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Active Downloads"
image:nil //[UIImage imageNamed:#"view1"]
tag:1];
completedDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Completed Downloads"
image:nil //[UIImage imageNamed:#"view3"]
tag:2];
[self setViewControllers:[NSArray arrayWithObjects:activeDL, completedDL, nil]];
However, it is not passing the current active download. I don't know if it's a initialization problem, or my tab issue of showing the current download.
From his github, he suggests to get the current number of downloads is to call: dlvc.numberOfDownloads which for me would be
[activeDL numberOfDownloads].
I call this in the the Downloader viewWillAppear but nothing shows.
Does anybody has any suggestions or have worked with this controller?
Any help would be appreciated.
When you call:
activeDL = [[HCDownloadViewController alloc] init];
You are creating a new download controller, which has its own internal downloads array. This library, as written, has no way to pass this information from one HCDownloadViewController object to another.
Tying downloads to VC's like this will cause problems -- I recommend you rewrite this code to split that apart.
To hack around it, try to create just one HCDownloadViewController object and pass it around.
Ok so with the last comment of the other answer, "Make activeDL a member variable instead of a local variable.", got me Googling and with some tinkering and bug fixing along the way I managed to get it all up and running perfect.
I declared it all in my AppDelegate.
AppDelegate.h
#interface SharedDownloader : HCDownloadViewController <HCDownloadViewControllerDelegate>
+ (id)downloadingView;
#end
AppDelegate.m
static HCDownloadViewController *active;
#implementation SharedDownloader
+ (id)downloadingView {
if (active == nil)
active = [[HCDownloadViewController alloc] init];
return active;
}
#end
Calling to the class for downloading in my main view controller:
-(id)init{
activeDL = [SharedDownloader downloadingView];
return self;
}
//Spot where I fire the download
if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
//More code here
[activeDL downloadURL:fileURL userInfo:nil];
}
Lastly in my tab bar controller:
-(id)init {
activeDL = [SharedDownloader downloadingView];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
activeDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Active Downloads" image:nil] tag:2];
}
I believe that's all of it. In any case, thanks to Lou Franco for pointing me in the right direction.

where to init a UITablewView as a subview

Where should I initialize a UITablewView as a subview, in - (id)initWithFrame:(CGRect)frame, in viewdidload or loadView? Which is the better approach? Where should I make the frame (I mean which is more effective)?
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = kViewBackgroundColor;
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
_tableView.backgroundView = nil;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.bounces = NO;
[self addSubview:_tableView];
}
return self;
}
On my projects I usually create a baseViewController with a custom initialiser like this:
- (id)init{
self = [self initWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
if (self) {
//You should create the tableView and other properties here
//and add as subviews inside viewDidLoad
_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0,0,100,100)];
}
return self;
}
and I always create my view controller by using this init method, because I don't think the other vc's need to know the name of the nib file. If I were you I would create subviews inside init method, add as subviews inside viewDidLoad or viewWillAppear and finally releasing them inside dealloc if you are not using ARC.
Generally this depends on your requirement that if you are having custom view called with a tableview as subview then loadview with the initwithframe method will be better and while you want to initialize it from viewcontroller then viewdidload is better. I think you should bifurcate your requirement. Hope this helps.
Since you are initializing table view with zero frame it wont be visible even if you added that
you can try this
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = kViewBackgroundColor;
UITableView *_tableView = [[UITableView alloc] initWithFrame:self.bounds style:UITableViewStyleGrouped];
_tableView.backgroundView = nil;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.bounces = NO;
[_tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth]
[self addSubview:_tableView];
}
return self;
}
If you'd like to create subviews programmatically based on superview metrics (frame or bounds), you should do it in viewDidLoad, because if you do in in initializer, metrics of self.view will not be avaliable, and you subviews will be created with zero frame and will not be visible (as Johnykutty) mentioned above.
But that does not seem to be a good practice in case your subview initialization routines require heavy operations.
Offtopic: I've been codin' for a looong time trying to do everything programmatically, never using xibs, etc. Well, if you ask me now, xibs really save your time and make life easier. Consider using interface builder for layout\autolayout, colors\borders etc stuff.

Passing Data from Slide menu to UITableViewController through UINavigationViewController

How can I pass data from UINavigationController to The root UITableViewController?
I have implemented the ECSlidingViewController (https://github.com/edgecase/ECSlidingViewController). User selects one of the cells in the menu that correspond to different urls I want to display information from on my tableView that sitts on top of the UINavigationController. (u know the default combination that u get my dragging UINavigationController to ur storyboard). I am able to get the data from the sliding menu to my navigationController now I am trying to pass that same info on my tableview?
In my menu I have:
UINavigationController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"NavigationTop"];
newTopViewController = [(NavigationTopViewController*)newTopViewController initWithCinema:self.myCinema];
In UINaviationController:
- (id)initWithCinema:(Cinema *)cinema {
self = [super init];
if(self) {
_myCinema = [[Cinema alloc] init];
_myCinema = cinema;
}
return self;
}
- (void) viewDidLoad {
[super viewDidLoad];
// this log works I get the info to here.
NSLog(#"url(navigation):%#", self.myCinema.cinemaURL);
//MoviesTableViewController *moviesTableViewController = [[MoviesTableViewController alloc] initWithCinema:self.myCinema];
//UITableViewController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MoviesTable"];
//NavigationTopViewController *newTopViewController = [[NavigationTopViewController alloc] initWithCinema:self.myCinema];
//newTopViewController = [(MoviesTableViewController *)newTopViewController initWithCinema:self.myCinema];
//[self performSegueWithIdentifier:nil sender:self.myCinema];
[self prepareForSegue:nil sender:self.myCinema.cinemaURL];
}
In my UITableView:
- (void)setCinema:(Cinema *)cinema {
// works here too
NSLog(#"Table(setCinema):%#", cinema.cinemaURL);
self.myCinema = [[Cinema alloc] init];
if(!cinema) {
cinema.cityIndex = kAstanaIndex;
cinema.name = kKeruen;
cinema.nameForText = kKeruenText;
cinema.cinemaURL = kKeruenURL;
cinema.cinemaURLTomorrow = kKeruenURLtomorrow;
}
self.myCinema = cinema;
// works here too!!!
NSLog(#"Table(myCinema):%#", self.myCinema.cinemaURL);
}
However its gone in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
// set delegate to self
self.tableView.delegate = self;
// set loading theater's url
// does not work here: I GET NULL !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
NSLog(#"url(moviesTable):%#", self.myCinema.cinemaURL);
_model = [[MovieModel alloc] initWithURL:self.myCinema.cinemaURL];
}
None of the methods I have tried (commented in Navigation worked...) at least for me. Please give me any suggestions. Thank you in advance.
UINavigationController does not hold any data, but rather a stack of view controllers. I'd recommend you check out frameworks such as the free Sensible TableView. The framework will automatically handle detail view generation and passing data between them. Saves me tons of development time in my projects.

Passing arguments with a selector to new .xib of a custom object

I have a tableview that will load a new .xib when pressed. The issue is, I need to pass a custom object over to the new .xib. So far, I have:
-(UITableViewCell *)doTestCellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
//some other code for current view
TestObj *testobj = [[TestObj alloc]init];
if(shouldGo){
cell.userInteractionEnabled = YES;
UITapGestureRecognizer *tapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(loadTestView:)];
tapped.numberOfTapsRequired = 1;
[cell addGestureRecognizer:tapped];
}
And in that method:
-(void)loadTestView:(TestObj *)withTestObj{
TestViewController *newView =
[[TestViewController alloc] init];
//Set the property of type TestObj
newView.testobjprop = withTestObj;
[self presentViewController:newView animated:YES completion:nil];
}
It throws NSInvalidArgumentException. How can I make it to where the selector will send "testobj" that was initialized in the tableview method so I can access it there?
The new .xib loads in just fine if I comment out "newView.testobjprop = withTestObj;" in the loadTestView method. So I assume the issue lies there, and it is a matter of how to pass an object from one .xib to another.
The argument from -(void)loadTestView:(TestObj *)withTestObj is UIGestureRecognizer type so that's why you get NSInvalidArgumentException.
If you change it to -(void)loadTestView:(UITapGestureRecognizer *)withTestObj it will be the normal behavior.
That's why you need to use didSelectRow:AtIndexPath so you can pass the correct value to the viewcontroller.
Here you aren't using TestObj *testobj = [[TestObj alloc]init]; at all.

When do I need to call -[UIViewController initWithNibName:bundle:]?

In post Using initWithNibName changes absolutely nothing, he shows two uses of the same View Nib definition, in the first case, he simply calls alloc/init and the second, he specifies initWithNibName.
So, while this always works:
MyViewController *vctrlr = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:vctrlr animated:YES];
[vctrlr release];
The following works for all the View Controllers I've inherited, but not mine!
TheirViewController *vctrlr = [[TheirViewController alloc] init];
[self.navigationController pushViewController:vctrlr animated:YES];
[vctrlr release];
New to iOS programming, I inherited some code. All the View Controllers' views are defined in IB, but there was inconsistent allocation/init creation of those view controllers. I created a new View Controller and XIB, but it does not work unless I use initWithNibName (it crashes when I push the view controller onto the Nav Controller). I cannot tell how my view controller is different than the others... any hints? I was able to delete the initNibName usage for all the other view controllers in the app except mine.
You can pass any string name to initWithNibName:. You are not just restricted to calling initWithNibName:#"MyClassName" when your class is called MyClassName. It could be initWithNibName:#"MyClassNameAlternateLayout".
This becomes useful if you need to load a different nib depending on what the app needs to do. While I try to have one nib per view controller per device category (iPhone or iPad) whenever possible to make development and maintenance simpler, I could understand if a developer would want to provide a different layout or different functionality at times.
Another important point is that initWithNibName:bundle: is the designated initializer for UIViewController. When you call -[[UIViewController alloc] init], then initWithNibName:bundle: is called behind the scenes. You can verify this with a symbolic breakpoint. In other words, if you simply want the default behavior, it is expected that you can call -[[UIViewController alloc] init] and the designated initializer will be called implicitly.
If, however, you are calling -[[UIViewController alloc] init] and not getting the expected behavior, it's likely that your UIViewController subclass has implemented - (id)init incorrectly. The implementation should look like one of these two examples:
- (id)init
{
self = [super init];
if (self) {
// custom initialization
}
return self;
}
or
- (id)init
{
NSString *aNibName = #"WhateverYouWant";
NSBundle *aBundle = [NSBundle mainBundle]; // or whatever bundle you want
self = [self initWithNibName:aNibName bundle:aBundle];
if (self) {
// custom initialization
}
return self;
}
If you want to work following code:
MyViewController *vctrlr = [[MyViewController alloc] inil];
[self.navigationController pushViewController:vctrlr animated:YES];
Then you should implement following both methods in MyViewController:
- (id)init
{
self = [super initWithNibName:#"MyViewController" bundle:nil];
if (self != nil)
{
// Do initialization if needed
}
return self;
}
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
NSAssert(NO, #"Init with nib");
return nil;
}

Resources