Properties set in init are nil right after in viewDidLoad? - iphone

I'm running into an issue where a derived class sets a property defined in the super class in its initWithNibName call and by the time viewDidLoad is called, the property is magically undefined again. It goes something like:
#interface BaseClassController : UIViewController
{
}
#property (copy, nonatomic) NSString *myString; // synthesized in the .m
in another file
#interface DerivedClassController : BaseClassController
{
}
and in its implementation:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
NSLog(#"Setting MyString stuff");
self.myString = #"Hi Mom";
NSLog(#"Done setting myString");
}
return self;
}
- (void)viewDidLoad
{
NSLog(#"MyString is %#", self.myString); // output as (null)!!!
[...]
}
According to the output of NSLog the order is correct, I know for a fact that viewDidLoad isn't being called first: first the initWithNibName method, then viewDidLoad. viewDidLoad reports self.myString as (null) every single time. However if I move the initialization of myString down to viewDidLoad, everything works fine. Why? I must be missing something really obvious here..
Edit:
Not sure if this makes a difference, but the DerivedClassController is the root view controller of a navigation controller that's being presented modally (for all sorts of legacy reasons).
Edit 2:
The code works as expected in a stand-alone xcode project, it fails when imported into the project where I'll ultimately need to use it.

Are you certain that you're logging from the same controller both times? Like, perhaps you're calling initWithNibName:bundle: on one controller, but then viewDidLoad is actually called on another controller?

Is there any reason why you have it set to copy?
Try setting the property to:
#property (nonatomic, strong) NSString *myString;

Related

How to access values from a different UIViewController

How can I access the value from an inputField located in a second viewController?
The class name of the second view controller is SettingsViewController and the outlet name for the inputField is setRateInput.
I tried this but it didn't work…
double taxRateFromInput = [[self.settings.setRateInput text]doubleValue];
when I NSLog it comes out as The value is: (null)
Any idea what I'm doing wrong?
Here is the implementation file for the main viewController:
#import "SettingsViewController.h"
#interface ViewController ()
#property (strong, nonatomic) SettingsViewController * settings;
#end
#implementation ViewController
// lazy instantiation
-( SettingsViewController *) settings
{
if (_settings == nil) {
_settings = [[SettingsViewController alloc]init];
}
return _settings;
}
- (IBAction)calculatePrice:(id)sender {
double taxRateFromInput = [[self.settings.setRateInput text]doubleValue];
#end
In theory, you could create a global. Create a new class, call it something like taxRate (.h and .m)
In taxRate.h, add the following code:
#import <Foundation/Foundation.h>
#class MyTaxRate;
#interface TaxRate : NSObject {
}
#property (nonatomic, retain) double * taxRateFromInput;
+(TaxRate*)getInstance;
#end
Then, in your controller, put a "#import taxRate.h" in there. In your .m file, add the following:
#import "TaxRate.h"
#implementation TaxRate
#synthesize taxRateFromInput;
static TaxRate *instance =nil;
+(TaxRate *)getInstance
{
#synchronized(self)
{
if(instance==nil)
{
instance= [TaxRate new];
}
}
return instance;
}
#end
Note: This is extremely similar in structure to what I'm purposing.
if you have the reference from the object view controller you can just access by the property from your attribute.
You instantiated a new SettingsViewController, but you didn't do anything to instantiate its textfield setRateInput. You can do it when you instantiate it:
_settings = [[SettingsViewController alloc]init];
_settings.setRateInput = [UITextField alloc] initWithFrame:someFrame]];
or, as a beter solution, instantiate the text field in -init of SettingsViewController
- init {
if (self = [super init] {
self.setRateInput = [UITextField alloc] initWithFrame:someFrame]];
}
return self;
}
If you use nib files, this would be a lot easier.
Note: setRateInput is a bad name for a property. Consider rateTextField instead.
Edit I forgot to add that you have to add the text field as a subview to its parent view.
So it will be like,
_settings = [[SettingsViewController alloc]init];
_settings.setRateInput = [[UITextField alloc] initWithFrame:someFrame] autorelease];
[_settings.view addSubView:_settings.setRateInput];
In this case, the setRateInput is retained by its super view. You're not using ARC, so you can call autorelease on your text field.
The better solution: Use - (void) loadView; inside SettingsViewController. Loading the view is the responsibility of the correspondent view controller.
- (void) loadView {
self.setRateInput = [[UITextField alloc] initWithFrame:someFrame] autorelease];
[self.view addSubView:_settings.setRateInput];
}
Edit: xib files and storyboards can help you out. Give these tutorials a try.
You are on the right track, also well done with your lazy instantiation (as
a demonstration that you grasped the concept, I mean).
But note, that outlets don't get connected until viewDidLoad is called. So if you
just alloc/init your viewController (lazily), the outlet to your textfield is pointing to nil.
The outlet doesnt get connected until your controller's view property is accessed, ie the view is displayed.
What you could do is give the settings viewController a handle to your calculating viewController and let it set a public property on the calculating viewController that represents the rate.
This is a common pattern - delegation - where one viewController (settingsViewcontroller) calls a method on its delegate (calculating viewController).
You wouldn't need the settingsViewcontroller property in your calculating viewController then, but just instantiate a new settings viewController every time you want it to be brought up, giving it a reference to your calculating viewController.
Another possibility - maybe even better - is to define a model object that does calculation and takes care of the rate it needs to calculate. Then you could give your settingsViewcontroller a reference to that model object (probably instantiated in your
other viewController), so that it can change the rate on it.
PS: also re think how you instantiate viewControllers generally. The designated initialiser is -initWithNibName:bundle: - so usually, you wouldn't just alloc/ -init them.
If you use storyboards (you probably should!), use storyboard's -instantiateViewControllerWithIdentifier: or use the above mentioned designated initialiser.

event delegation

I have two uiviewcontroller: MainViewController and SecondaryViewControlle. In MainViewController I do:
[self.view addSubView:SecondaryViewControlle.view];
The SecondaryViewController is a button by pressing the function to be performed by a MainViewController. How to do?
You'd start by defining a protocol in your SecondViewControlle.h file, something like:
#protocol SecondViewControlleDelegate
- (void) doSomething
#end
You would also need to add a "delegate" ivar to your SecondViewControlle .h file. It would be the delegate line:
#interface SecondViewControlle : UIViewController
...
...
...
#property (nonatomic, assign) id delegate; // all you need to do is add this line inside your interface declarations
...
...
...
#end
Then, when you create / instantiate your SecondaryViewControlle from your MainViewController, make certain to add the MainViewController as the delegate like so:
SecondaryViewControlle.delegate = self;
[self.view addSubView:SecondaryViewControlle.view];
Now the "delegate" of your SecondaryViewControlle view controller points back to your MainViewController.
And when the button is pressed, you can simply do something like:
- (IBAction) buttonIsPressed: (id) sender
{
[delegate doSomething];
}
Now, I need to give you some advice here.
1 ) DO NOT use the class names as object names. Instead of having an object named "SecondViewControlle", name it something different (and start it with a lower case, which is Objective-C convention), something like "moreDetailVC".
2) I've told you how to do this with a delegate pattern, but this may not be the most appropriate way to do whatever it is that you're trying to do. After all, the MainViewController object (which should be renamed mainVC to differentiate the object from the class) is not on screen or visible so maybe there's a better place to put the functionality?
Option A
It's quicker, and easier, but lacks the maintainability, since there is no contract stating that SecondaryViewController needs to bother calling anything, and self.parentViewController could be any UIViewController.
Option B
The delegate pattern; this is my preference, it's obvious what's happening, what's required, and there's a nice solid contract that states, if you want to initialise me, give me a delegate.
Option C
If SecondaryViewController has to notify multiple objects, it would be quick to use the NSNotificationCenter, but as with Option A, there's no contract, should you need to notify many objects, you would need to remember to listen for notifications on those objects - since this is not the question, I won't go into detail, it's just here for the information
Option A
Within MainViewController.m, do something like so:
SecondaryViewController *viewcontroller = [[SecondaryViewController alloc] initWithNibName:#"SecondaryView" bundle:nil];
[self addChildViewController:viewcontroller];
//set viewcontroller.view frame
[self.view addSubview:viewcontroller.view];
[viewcontroller didMoveToParentViewController:self];
Inside MainViewController.h
-(void) performButtonClickAction;
Inside MainViewController.m:
-(void) performButtonClickAction {
//Do something constructive
}
and then inside the SecondaryViewController.m:
-(IBAction) buttonPressed:(id) sender {
[self.parentViewController performButtonClickAction];
}
Option B
Inside SecondaryViewController.h
#protocol SecondaryViewControllerDelegate <NSObject>
-(void) eventAFromViewController:(UIViewController *) viewController;
-(void) eventBFromViewController:(UIViewController *) viewController;
#end
#interface SecondaryViewController : UIViewController {
id<SecondaryViewControllerDelegate> delegate;
}
#property (assign, nonatomic) id<SecondaryViewControllerDelegate> delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(id<SecondaryViewControllerDelegate>) theDelegate;
#end
Inside SecondaryViewController.m
#synthesize delegate = _delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(id<SecondaryViewControllerDelegate>) theDelegate
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.delegate = theDelegate;
}
return self;
}
-(IBAction) buttonPressed:(id) sender {
if( self.delegate != nil ) {
[_delegate eventAFromViewController:self];
}
else {
//No delegate
}
}

customize initWithNibName

I want to have a custom initWithNibName, basically passing in another NSString as a type to determine some logic inside this UIViewController based on the type. So I set is as follows:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andFeedType:(NSString *)feedType
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
Does this even make sense? As I don't see this type of init's quite often. If not then what is the best way to do this?
It makes perfectly sense to me. This is the way you would override an initializer to add some custom initialization in Objective-C. What do you think is wrong with it ?
Yes it makes sense. In addition, if you want to keep clean your init you could do the following:
- (id)initWithFeedType:(NSString *)feedType
{
self = [super initWithNibName:#"YourNibName" bundle:nil]; // nil is ok if the nib is included in the main bundle
if (self) {
// Set your feed here (copy it since you are using a string)
// see the edit
myFeedType = [feedType copy];
}
return self;
}
For further info see the post initWithNibName-bundle-breaks-encapsulation by Ole Begemann.
Hope that helps.
Edit
If that feed property cannot be accessed by external objects, create a class extension for your controller like the following:
//.m
#interface YourController ()
#property (nonatomic, copy) NSString* myFeedType;
#end
#implementation YourController
#synthesize myFeedType;
#end
It does make sense. You are creating you own initializer, tailored to your needs. Also, you are doing what you should, which is calling the designated initializer (in the case of UIViewController initWithNibName:bundle:) inside your custom init method.
Wait!
There is one reason why it may not be best: this method is not called when your view controller is loaded from a storyboard. For this reason I recommend using viewDidLoad: instead for the custom logic, and setting your custom string as a property.

NSDictionary setting to nil when passed to another class (IOS)

I am passing an NSDictionary object from one view class to another as I transition from a table view to a normal view to show details:
Passing Controller:
[tweetController setTweet:tweet];
Receiving Controller.h:
#interface TweetViewController : UIViewController {
NSDictionary *tweet;
}
#property (nonatomic, retain) NSDictionary *tweet;
Receiving Controller.m:
#implementation TweetViewController
#synthesize tweet = _tweet;
I then try to use this information to set the properties of some fields in my view:
- (void)viewDidLoad
{
[super viewDidLoad];
tweetLabel.text = [_tweet objectForKey:#"text"];
}
The result is a blank label and if I inspect the value of _tweet at this stage it is nil.
I originally had a method which set the value of tweet which I called at the same location as I am now setting the value. If I inspected the value at this stage it was fine.
I presume that the automagic setter through #synthasize is working, but somewhere else the value is being lost.
Sorry this is my first objective C anything! Thanks for any help in advance.
You are using your "tweet" instance variable, whereas the "tweet" property is synthesized to the "_tweet" variable.
You are probably calling the setTweet method after viewDidLoad executes.
I usually pass this kind of thing into a custom init method.
Alternatively, you could do the set before pushing the detail VC onto the nav stack.
Are you sure that tweetLabel isn't nil?
I've made a few corrections & optimisations to your code. You don't need to declare ivars in the header file anymore, they are generated automatically by #synthesize
- (void)dealloc; is only needed if you're not using ARC.
//.h
#interface TweetViewController : UIViewController
#property (strong, nonatomic) NSDictionary *tweet;
#property (strong, nonatomic) IBOutlet UILabel *tweetLabel
#end
//.m
#implementation TweetViewController
#synthesize tweet = _tweet;
#synthesize tweetLabel = _tweetLabel;
- (void)viewDidLoad {
[super viewDidLoad];
self.tweetLabel.text = [self.tweet objectForKey:#"text"];
}
- (void)dealloc {
[_tweet release];
[_tweetLabel release];
[super dealloc];
}
#end
Note: strong is equivalent to retain
To expand on #Rayfleck's answer, since you are new to Objective-C, your custom init method could look like this:
In TweetViewController.h:
- (id)initWithTweet:(NSDictionary*)tweet;
In TweetViewController.m:
- (id)initWithTweet:(NSDictionary*)tweet
{
self = [super init];
if (self) {
_tweet = tweet;
}
return self;
}
and then in your passing controller you'd allocate and initialize like this:
TweetViewController *tvc = [[TweetViewController alloc] initWithTweet:myTweet];

Pass UITextView contents to ModalViewController from parent view

I have a view with a UITextView and a button, both connected via an IBOutlet. Pressing the button brings up a ModalViewController. I need to pass the text in the UITextView to the ModalViewController as a string. I've done a bit of Googling but could only find info on passing strings from the ModalViewController to the parent view.
PS: Forgive my newbieness :p
Is the ModalViewController object created and presented in an IBAction method? If so, in that method you should query the UITextView for its value (textView.text) and pass it to the ModalViewController.
You could subclass init with nib:bundle to include a string for the mycontroller view controller. So that in it's .h you will have
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andstring:(NSString *)string;
and in the viewcontroller's .m you will have
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andstring:(NSString *)string
{
//initialize self here from bundle;
//do other stuff
self.text=string}
assuming you define a NSString *text propery in the header of the view controller which you present modally;
Then in where you define my controller you would do
MYVCController *mycontroller=[[MYVCController alloc] initWithNibName:nibname bundle:bundlename andstring:textView.text];
Either you need to create a property for setting a variable inside the modal view controller, or you can define a method and do that.
e.g.
#interface ModalViewController : UIViewController {
NSString *displaystr;
}
#property (nonatomic, retain) NSString *displaystr;
...
#end
#implementation ModalViewController
#synthesize displaystr;
...
#end
Then you can use the property displaystr to store the text in the UITextField. You can get the text via the text property.
Make a NSString varible in appDelgate class and make it property.
NSString *value;
#property(nonatomic,retain) NSString *value;
and synthesize it in .m
#synthesize value;
Make object for you app delegate class and access that to set that value and get that value.
in modelViewController
write this code for button which you use to dismiss
yourAppDelegateClass *objDelegate=(yourAppDelegate *)[[UIApplication sharedApplication] delegate];
objDelegate.value=textView.text;
and by the object of app delegate you can access the value.