I have a uiviewcontroller with two properties: trackName and playerObject. PlayerObject also has a trackName property. I call this uiviewcontroller from my main uiviewController with this code:
SecondaryViewController *nextViewController = [[SecondaryViewController alloc] initWithNibName:#"SecondaryViewController" bundle:nil];
NSString *trackName = #"a track";
nextViewController.trackName = trackName;
[self.navigationController pushViewController:nextViewController animated:YES];
[nextViewController release];
In SecondaryViewController I override the initwithnibname method to set the trackName of the playerObject. I do this with this code:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
playerObject.trackName = trackName;
}
playerObject.trackName = trackName;
return self;
}
Finally my playerObject has all of the view data the SecondaryViewController will need. It looks like:
- (void)awakeFromNib{
NSString *s = trackName;
//more code relevant to the the view controller
}
When I debug, the trackName string in the playerObject is nil. I assume I'm doing something wrong. How can I have this value populated with the trackName I originally passed in the main uiview controller?
It seems like when you are initing the viewController the playerObject variable has not yet been set, could this be possible?
This can sometimes happen when you override initWithNibName:bundle:.
Instead use viewDidLoad to do setup. Apple guarantees all required setup is performed before this method is called (not the case with initWithNibName:bundle:).
Related
I have a really strange delegate behavior in iOS. I'm setting a custom delegate in a subclassed UIViewController like this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.baseNavigationBar = [[BaseNavigationBar alloc] initWithNibName:nil bundle:nil];
self.baseNavigationBar.delegate = self;
self.baseNavigationBar.navigationController = self.navigationController;
[self.navigationController.navigationBar addSubview:self.baseNavigationBar];
}
The initWithNibName can use nil, when using the default nib, because internally it will check fora nil in the nibName.
if (!nibNameOrNil) {
nibNameOrNil = NSStringFromClass([self class]);
}
The delegate declaration from the baseNavigationBar object looks the following:
#property (nonatomic, retain) id<BaseNavigationBarDelegate> delegate;
/*same thing with assign, which I you should use*/
/*and in the .m of course #synthesize*/
And now 2 screenshots from the running application.
The first one show's the debugger values from the BaseListViewController, which is a subclass of BaseCoreViewController, which is a subclass of UIViewController.
The screenshot is taken, when the viewDidLoad method is called.
The second one show's the values from the BaseNavigationBar, which is a UIView subclass.
The screenshot is taken at a time, when the user clicks the "next" button
- (IBAction)nextAction:(id)sender {
if (self.delegate) {
[self.delegate navigationBarDidClickNextButton:self];
}
}
So why is this a problem? By clicking a button in the BaseNavigationBar my delegate is always nil, so the program is stuck. But when looking at the values from the BaseCoreViewController at the same time the delegate is not nil. Very strange.
Edit
The BaseNavigationBar is loaded from a nib file using the UINib loadNibNamed:owner:options: function.
Edit 2
So that's pretty much all of the code.
Edit 3
Finally we got the source of the error in the last pieces of the code... setting self = something totally not allowed...
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (!nibNameOrNil) {
nibNameOrNil = NSStringFromClass([self class]);
}
if (!nibBundleOrNil) {
nibBundleOrNil = [NSBundle mainBundle];
}
NSArray *topLevelObjects = [nibBundleOrNil loadNibNamed:nibNameOrNil owner:self options:nil];
/*Here is the bad part*/
self = [topLevelObjects objectAtIndex:0];
state = BaseNavigationBarButtonStateNext;
if (self) {
self.title = #"";
}
return self;
}
Solved using a Class method instead of defining a custom init.
If you are using nib file, you should declare the nib class while initialising your class object.
You have initialised initWithNibName:nil
self.baseNavigationBar = [[BaseNavigationBar alloc] initWithNibName:nil bundle:nil];
but it should be like initWithNibName:#"BaseNavigationBar"
self.baseNavigationBar = [[BaseNavigationBar alloc] initWithNibName:#"BaseNavigationBar" bundle:nil];
Not sure what the problem is. Require some more code to analyze the problem. But what I can see is address of BaseNavigationBar in both the screen shots is different. It means the BaseNavigationBar object present in the controller is not the one which got the nextAction event as in second screen shot.
I've done this many times with code that is exactly the same, but for some reason it isn't working today.
ExampleViewController1 *exampleView = [[ExampleViewController1 alloc] initWithNibName:#"ExampleViewController1" bundle:nil];
[exampleView setProjectName:[[self.projectListArray objectAtIndex:indexPath.row] objectForKey:#"name"]];
NSLog(#"%#", [[self.projectListArray objectAtIndex:indexPath.row] objectForKey:#"name"]);
XAppDelegate.stackController pushViewController:exampleView fromViewController:nil animated:YES]
My NSLog prints out appropriately.
My ExampleViewController1.h file declared like:
#property(nonatomic, strong) NSString *projectName;
I then do this code in ExampleViewController1.m's
-(void)viewDidLoad {
NSLog(#"%#", self.projectName);
self.projectNameLabel.text = self.projectName;
[super viewDidLoad];
}
The results of my NSLogs are curious. The NSLog from my viewDidLoad appears to be getting called before my other one:
2012-04-22 10:59:41.462 StackedViewKit[43799:f803] (null)
2012-04-22 10:59:41.463 StackedViewKit[43799:f803] NewTest
I have confirmed that the (null) value there is from NSLog(#"%#", self.projectName);, but that should be the second NSLog called...I can't figure out why it is coming through first.
Someone requested this code:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// random color
self.view.backgroundColor = [UIColor colorWithRed:((float)rand())/RAND_MAX green:((float)rand())/RAND_MAX blue:((float)rand())/RAND_MAX alpha:1.0];
}
return self;
}
As I expected, the problem is that you are trying to access self.view inside the initialization method. So move the line self.view.backgroundColor = ... to the viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", self.projectName);
self.projectNameLabel.text = self.projectName;
self.view.backgroundColor = [UIColor colorWithRed:((float)rand())/RAND_MAX green:((float)rand())/RAND_MAX blue:((float)rand())/RAND_MAX alpha:1.0];
}
In fact, the documentation of the view property says:
If you access this property and its value is currently nil, the view controller automatically calls the loadView method and returns the resulting view.
So when you call self.view in the initialization method, the view controller will have to load the view (from the nib or using the loadView method). And that's why viewDidLoad is called.
viewDidLoad is called before a view controller is displayed for the
first time, not immediately after initWithNibName.
> viewDidLoad method is called after the view controller has loaded its view
hierarchy into memory. This method is called regardless of whether the
view hierarchy was loaded from a nib file or created programmatically
in the loadView method.
> initWithNibName The nib file you specify is not loaded right away. It
is loaded the first time the view controller’s view is accessed. If
you want to perform additional initialization after the nib file is
loaded, override the viewDidLoad method and perform your tasks there.
You can use App delegate to pass the data from one to another, that is another alternate solution.
you do in initWithNibName method itself. or in viewDidAppear.
Your initWithNibName method should be like this as per as #sch comments;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil] //just set it here first and then check
if (self) {
// do something here;
}
return self;
}
We just need to be smart enough to think about what do we need to in constructor and what do we need to at viewDidLoad (once it had loaded into memory)
my app starts with a tab bar controller which have 5 tabs. At start the first one in presented with its name but the other four don't have a name until I click on them. Then the name appears depending which language the user has.
How can I set the name of the tabs before the tab bar appears?
I am using storyboard. Is there a way to set title at the tab bar programmatically when all the rest is done with storyboard? I tried in the AppDelegate something like [FirstViewController setTitle:NSLocalizedString(#"Titel1", nil)];
But I got an error that there is no class method for selector setTitle.
Thanks.
I had the same problem today. I'm using storyboard too. In my case i used the following way.
I've created a new "Objective-C class" with the name
"MainTabBarViewController" as a subclass of "UITabBarController".
In my storyboard in "identity inspector" i changed "Custom class" to "MainTabBarViewController".
In the method "viewDidLoad" in "MainTabBarViewController" i added:
[[self.viewControllers objectAtIndex:0] setTitle:NSLocalizedString(#"home", nil)];
[[self.viewControllers objectAtIndex:1] setTitle:NSLocalizedString(#"statistics", nil)];
[[self.viewControllers objectAtIndex:2] setTitle:NSLocalizedString(#"settings", nil)];
[[self.viewControllers objectAtIndex:3] setTitle:NSLocalizedString(#"info", nil)];
I guess it is not the perferct way, but for me it works perfect.
I had a configuration with two tabs like this:
MainTabBarController : UITabBarController
+- MessagesNavigationController : UINavigationController
+- ContactsNavigationController : UINavigationController
In a configuration with Storyboard and ARC I overrode the initWithCoder: selector in the custom classes of my UITabBarController view controllers (in this case ContactsNavigationController) and initialized the tabBarItem.title there like this:
#implementation ContactsNavigationController
...
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super initWithCoder:decoder];
if (self) {
self.tabBarItem.title = NSLocalizedString(#"Contacts", nil);
}
return self;
}
When using storyboards the iOS calls (id)initWithCoder: instead of -(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil when the storyboard view controller is initialized during UITabBarController launch. Also note that viewDidLoad doesn't get called until the view controller tab is selected from the tab bar.
In the app delegate, where you are creating the view controllers, set the title property here (rather than in viewDidLoad), for example:
vc1 = [[VC1 alloc] init];
vc1.title = #"List";
vc2 = [[VC2 alloc] init];
vc2.title = #"Map";
tabBarController.viewControllers = [[NSArray alloc] initWithObjects:vc1, vc2, nil];
If you take a look at the FirstViewController created by Xcode using the template Tab-Bar application based in the - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil method you can see something like this:
if (self) {
//check your language
self.title = NSLocalizedString(#"First", #"First");
self.tabBarItem.image = [UIImage imageNamed:#"first"];
}
So you have to check for your language and then set the title property, this will set the title on the tab bar and in the navigation bar.
Try this, in every view u have to use this method
Suppose this is you first view
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"homepage", #"homepage");
[self.tabBarItem setTag:0];
self.tabBarItem.image = [UIImage imageNamed:#"homepage"];
}
return self;
}
In you app delegate class write this code to add UITabBarController
self.tabBarController = [[[UITabBarController alloc] init] autorelease];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, viewController2,viewController3,viewController4,viewController5, nil];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
Add above code in
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {}
In my navigation based application, initWithNibName method does not called/fire
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
I'm defining rootViewController (calViewController) in appdelegate like this
calViewController *objCalViewController = (calViewController *) [navController topViewController];
objCalViewController.context = [self managedObjectContext];
[window addSubview:navController.view];
[window makeKeyAndVisible];
Is this is the issue? Please give me a help
When your UIViewController is defined in a nib file or Storyboard (usually as an IBOutlet), initWithNibName:bundle: is not called, rather initWithCoder: is. This is the case when you use Interface Builder to set your UIViewController as part of UITabBarController or UINavigationController, and almost always when using Storyboards.
in your appdelegate.m do like this
calViewController *objCalViewController = [[calViewController alloc] initWithNibName:#"WebViewController_iPhone" bundle:nil];
objCalViewController.managedObjectContext = self.managedObjectContext;
navController = [[UINavigationController alloc] initWithRootViewController:objCalViewController];
[self.window setRootViewController:navController];
[window makeKeyAndVisible];
Ok lets say i have a viewController named TCViewController with
TCViewController.h, TCViewController.m and TCViewController.xib
and in TCViewController.m
i am overriding the below method.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
In my RootViewController i want to initialize TCViewController like this
RootViewController.m
-(void)viewDidLoad //not necessarily this one, can be any method
{
// This initialization calls the initWithNibname method implemented in TCViewController.m
TCViewController *viewController = [[TCViewController alloc] initWithNibName:#"TCViewController" bundle:nil];
[self.navigationController pushViewController:viewController animated:YES];
}
If you initialize a viewController like this, control will pass to the initWithNibName method in your subclass if you have implemented it.
I have a question regarding how to pass around NSManagedObjectContext. In my app, it seems like it's my AppDelegate that handles NSManagedObjectContext, so I shouldn't create other NSManagedObjectContexts in my other ViewControllers.
So the question is...
There is any convention or smart method to do this?
Thanks.
The way I pass the NSManagedObjectContext is to simply have an iVar in each view controller you pass it to. I usually modify the initialiser to include assignment, something like this....
MyNewViewController.h
#interface MyNewViewController : UIViewController {
NSManagedObjectContext *managedObjectContext;
}
...
MyNewViewController.m
#implementation MyNewViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andContext:(NSManagedObjectContext *)ctx {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
managedObjectContext = ctx;
}
return self;
}
....
Then when you call the view controller, you simply use the modified initialiser. Like...
MyNewViewController *nv = [[MyNewViewController alloc] initWithNibName:#"MyNewViewController" bundle:nil andContext:self.managedObjectContext];
Now you've got a reference to the managedObjectContext which you can use within your view controller.