Setting a property from another property's setter from segue Objective-C - objective-c

For reference, I'm trying to learn Objective-C through the Stanford iTunes-U course. I wanted to update one property from the setter of another (they are inherently connected. Also, is that bad style?). The property I am trying to update is a UILabel, but it doesn't work like I thought.
This code is in one of my view controllers:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
[segue.destinationViewController setProgram:self.brain.program];
}
Naturally, this code calls the setter for the Program property of the incoming viewController. Here's that setter code:
-(void)setProgram:(id)program {
_program = program;
self.title = [CalculatorBrain descriptionOfProgram:program];
[self.graphview setNeedsDisplay];
self.testLabel.text = #"Trying to update your text!";
}
and the header file:
#import <UIKit/UIKit.h>
#interface GraphViewController : UIViewController
#property (nonatomic, strong) id program;
#property (weak, nonatomic) IBOutlet UILabel *testLabel;
#end
Now, when this code is run, as the segue happens, the new view DOES have its title changed (the self.title line works). However, the UILabel DOES NOT get updated. Furthermore, if I call the setter again say in viewDidLoad, it does change the UILabel. Is this because of self being updated? What's going on here?

In one of the lectures, the professor explains that outlets aren't set yet in prepareForSegue. This means testLabel is nil, and you're sending a message to nil (which is allowed and doesn't crash your app). To solve your problem, set the textLabel's text in viewDidLoad or viewWillAppear.
I believe this is "fixed" in iOS 6, but it won't be backwards compatible, so if you want to support iOS 5 still, you'll have to use viewDidLoad or viewWillAppear to update outlets (which I think is better to do anyway).

Related

IBOutlet is nil after initWithCoder is called

Simple problem, I have defined a UIImageView, called bigImageView in a UIViewController using the storyboard,
It's declared in the h file of that UIViewController as follows:
#property (retain, nonatomic) IBOutlet UIImageView *bigImageView;
on my appDelegate I init the UIViewController as follows:
imageViewController = [storyboard instantiateViewControllerWithIdentifier:#"chosenImageController"];
this calls initWithCoder on my UIViewController m file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Custom initialization
}
return self;
}
This function is only called once so there's no double init.
However, later, when I check my bigImageView pointer, it's still nil.
Isn't the init supposed to allocate memory to it?
I think that's why when I try to set this UIImageview to hold a UIImage it doesn't display the image
Thanks
It's all working how it's meant to. First every object in the nib/storyboard gets alloc/init called on them, then all the connections are made, and then viewDidLoad is called.
You need to wait for - (void)viewDidLoad to be called on your controller, and then bigImageView should be set. If it's not set then you did something wrong in the storyboard.
init methods are not responsible for allocating any memory. All memory is allocated by the alloc method which is always called before init. Alloc will fill all your instance variables with nil/NULL/0 values, and then init gives the chance to assign initial values to each one (based on the contents of the NSCoder object usually, but it's up to you to decide what should be done).
For IB outlets however, those are setup by the nib loading process after init.
EDIT:
// ViewControllerA.m:
imageViewController = [storyboard instantiateViewControllerWithIdentifier:#"chosenImageController"];
imageViewController.image = imageToShow;
// ViewControllerB.h
#property (retain) NSImage *image;
#property (retain, nonatomic) IBOutlet UIImageView *bigImageView;
// ViewControllerB.m
- (void)viewDidLoad
{
self.bigImageView.image = self.image;
[super viewDidLoad];
}
You don't need to define initWithCoder, since you have no custom logic in there. I would delete that boilerplate code.
Here is what I would check:
In the storyboard, ensure that the class of the view controller is set properly.
Ensure that the outlet is hooked up properly in the storyboard by looking for a circle near your #property. It should be a filled in circle, not an outline of a circle.
Make sure you are reading the value only after viewDidLoad is called. Apple's only guarantee is that the outlet is set after this method call.
Update: It sounds like you want to access the image view before the view is loaded. There is no way to do this. One hack is to call viewController.view which will force the view to load, but there are many reasons why you should not do this.
A better approach would be to implement properties on your view controller which work for both when the view is not loaded and when the view is loaded. You can see an example of an elegant solution in this question. Notice how if the view is loaded, the photographerLabel will get set via the didSet method. On the other hand, if the view is not loaded, it will get set via the viewDidLoad method. For an Objective-C version of that code or for more details, see the linked video in that question.

Pass a reference to ViewController in prepareForSegue

I have 2 view controllers, call them ViewController1 and ViewController2. A modal segue is invoked from ViewController1 when I want to load ViewController2. I have a method in ViewController1 that needs to be called at some point when ViewController2 is showing. My idea is to have a property in ViewController2 that is a reference to ViewController1 so that I can get access to the method.
#property (strong, nonatomic) ViewController1 *vc1Reference;
This property would be set in the prepareForSegue method like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // 1
if ([[segue identifier] isEqualToString:#"sequeToVC2"]) { // 2
ViewController2 *vc2 = segue.destinationViewController; // 3
vc2.vc1Reference = (ViewController1*)segue.sourceViewController; // 4
}
}
However line 4 gives me this error: Implicit conversion of an Objective-C pointer to 'int *' is disallowed with ARC.
How am I supposed to set the reference?
You are on the right track, but the correct way to do this is to use a delegate.
You declare a delegate property in your vc2 #interface:
#property (nonatomic, weak) id <vc2delegate> delegate //[1] (in vc2.h)
And you set the delegate in your prepareForSegue:
vc2.delegate = self; //[2] (in vc1.m)
('self' is the correct reference for vc1, from vc1)
In vc2 you define a protocol, which is the method that you expect vc1 to respond to from vc2. Put this in vc2.h, above your #interface
#protocol vc2delegate //[3] (in vc2.h)
- (void) delegateMethod;
#end
Then you have to ensure you implement that method in vc1. Also you need to let vc1 know to conform to the delegate. Import vc2 into vc1.h, and on your #interface line in vc1 add the protocol name in angle brackets:
#import vc2.h
#interface vc1 <vc2delegate> //[4] (in vc1.h)
This arrangement allows vc2 to pass a method to vc1 without having to #include vc1 or know anything else about it.
more detail...
This is the correct form of your
#property (strong, nonatomic) ViewController1 *vc1Reference;
Note the use of weak reference. You don't want to make a strong reference as you don't really want to have anything to do with the delegate except to know it can handle methods you specify in your protocol. The delegate is often the object that created the delegator, creating a strong reference back in the other direction can cause memory leaks as neither object can go out of existence.
this is the correct form of your line:
vc2.vc1Reference = (ViewController1*)segue.sourceViewController;
Note that we are NOT using type/casting in 1 or 2. For maximum code reuse/decoupling we dont want to make any suppositions on the type of object at either end of the segue.
I am assuming that your 'prepareForSegue' is in vc1. If it is not then the line would look like this:
vc2.delegate = segue.sourceViewController
This is the protocol declaration. It goes in the header file for vc2. vc2 is publishing it's expectations of any object that chooses to become its delegate. vc2 will be sending messages according to this protocol so any delegate needs to respond in the correct way. You can guard against failure in vc2 by using this kind of message-passing
if (self.delegate respondsToSelector:#selector('delegateMethod')
{
[self.delegate delegateMethod];
}
(that is an example of the kind of message passing you would use in vc2 to communicate back to vc1. you can obviously pass paremeters and get returned results back if need be)
this is a helper for the compiler which can issue you with warnings if you fail to implement the protocol.
Finally somewhere in your object definition you need to implement the method:
- (void) delegateMethod
{
// someaction;
}
I ran into something similar the other day. I ended up creating a delegate for vc2, and using
vc2.delegate = self;
in the segue instead. Would this solve your problem? If you need help setting up the delegate, let me know and I'll do my best to help!
Just add a delegate to your the ViewController that you are accessing the delegate on, XCode / Obj-C seems to do the right thing afterwards.
Before:
#interface ViewController2 : UIViewController
#end
After:
#interface ViewController2 : UIViewController
#property (nonatomic, weak) id <UIPageViewControllerDelegate> delegate;
#end

IBOutlet in ARC releases and sets to nil. How to avoid this? objective c

I'm new to ARC and Storyboarding. I've set IBOutlet to UITableView from my UIViewController.
After some time my IBOutlet sets to nil and I can't reload it from other classes.
Here is my dataTable IBOutlet:
#property (weak, nonatomic) IBOutlet UITableView *dataTable;
At the start dataTable is not nil, but not when I try to access it from another class (via appDelegate). How to solve this problem?
UPDATE
I call this method from my UIViewController
[appDelegate.myClass loginWithUserName:loginField.text andPassword:pwdField.text];
When it's done, and I have data to show, I call this code from loginWithUserName method:
MyViewController *controller = [[AppDelegate sharedStoryboard] instantiateViewControllerWithIdentifier:#"MyViewController"];
[controller audioLoaded];
And here is that method in my UIViewController, wich reloads data
-(void) audioLoaded
{
//it is nil here
[self.dataTable reloadData];
}
Set the property to strong retain the object:
#property (strong, nonatomic) IBOutlet UITableView *dataTable;
It's not good practice to access a UITableView from another view controller though..
EDIT:
You shoul reconsider the whole approach, by moving that logic from your appdelegate to a dedicated class that will perform the login. You can create a simple protocol that the UIViewController with the table can implement, then, when calling the login method, pass a reference to the current viewcontroller, something like
loginWithUserName:andPassword:andCaller:(id<LoginDelegate>)sender
Where LoginDelegate is something on this line:
#protocol LoginDelegate
- (void)audioLoaded;
#end
In this way you can just call
[sender audioLoaded];

Obj C delegate methods not assigned to UIButton (iOS)?

I am trying to assign a delegate's method to a UIButton using addTarget:action:forControlEvents:. Everything compiles without warnings, the IBOutlet is connected to the button in Interface Bulder (XCode 4). If I moves the delegate's method to the controller, it works fine. (All code worked fine, but I refactored to use a delegate, it's my first try with delegates and protocols.)
(added) The protocol declaration, placed before #interface in the .h:
#protocol MMGLVDelegate<NSObject>
-(void)receiveQuitRequest:(id)sender;
#end
In the controller interface, these properties:
#property (nonatomic, assign) id<TheDelegateProtocol> delegate;
#property (nonatomic, retain) IBOutlet UIButton *quitBtn;
In the controller implementation:
-(void)setDelegate:(id<MMGLVDelegate>)delegate {
DLog(#"MMGLVSegmented setDelegate: Entered");
_delegate = delegate;
[self.quitBtn addTarget:self.delegate action:#selector(receiveQuitRequest:)
forControlEvents:UIControlEventTouchUpInside];
}
Any help appreciated. Changing target to any of self.delegate, _delegate, or delegate doesn't change app behavior.
What I'm hoping to do is not have to declare a class receiveQuitRequest: that then passes off to the delegate, I'd rather go straight to the delegate from the control.
I think you should write
[self.quitBtn addTarget:delegate action:#selector(receiveQuitRequest:)
forControlEvents:UIControlEventTouchUpInside];
I am not sure, but this may work in your case
I have a couple of minor suggestions:
Try disconnecting and re-connecting the delegate for the button in IB. I've noticed that sometimes this seems to reset it properly.
Clean and rebuild. Again, this helps to reset things that didn't get set properly.
If I've understood everything correctly, the UIButton does not include a receiveQuitRequest: selector, so there is nothing to be executed when the user touches the button.

When to alloc/init an ivar, and when not to

All,
In Apple's sample code "DateCell"
http://developer.apple.com/library/ios/#samplecode/DateCell/Introduction/Intro.html
the ivar "pickerView" is declared in MyTableViewController.h like this:
#interface MyTableViewController : UITableViewController
{
#private
UIDatePicker *pickerView;
UIBarButtonItem *doneButton; // this button appears only when the date picker is open
NSArray *dataArray;
NSDateFormatter *dateFormatter;
}
#property (nonatomic, retain) IBOutlet UIDatePicker *pickerView;
...
It is synthesized in the class file MyTableViewController.m like this:
#implementation MyTableViewController
#synthesize pickerView, doneButton, dataArray, dateFormatter;
...
When this app runs, I can insert NSLog(#"%#",pickerView) into ViewDidLoad and see that, sure enough, the ivar pickerView is real and has a value. Nowhere, though, does this class alloc/init pickerView. And that's the root of the question: how's it getting done if it's not being done explicitly?
Well, I naively copied this stuff to my code into my RootViewController.h and .m files figuring I could do the same, but pickerView stubbornly remains uninitialized (and my NSLog calls return "(nil)" as its value) no matter what I try short of explicitly alloc/initing it. Certainly RootViewController is being instantiated, or the RootView wouldn't be showing up, right? So shouldn't my pickerView be coming along for the ride just as it does for Apple?
So... do I have to manually alloc/init the pickerView instance variable? If so, where's Apple doing it? Or how are they doing it somehow otherwise?
I think I'm missing something very basic here, but I have no idea what it is. I can't see anything in Interface Builder or XCode that looks different between mine and theirs, but I've got tunnel vision at this point and can't see anything clearly anymore.
Thanks,
Bill
The IBOutlet modifier on this line is the key...
#property (nonatomic, retain) IBOutlet UIDatePicker *pickerView;
IBOutlet is a decorator that indicates that the object will be hooked up/connected/initialised when the corresponding xib (Interface Builder) file is loaded. The sample application you're looking up will contain a UITableViewController is a xib which has a connection to a UIPickerView.
You can either go the route of creating your own custom xib file and wire to an instance of UIPickerView or you can manually initialise the picker yourself.
Interface Builder (nib or xib) treats automatically IBOutlet ivar with connection of components.
IBOutlet is a special keyword that is
used only to tell Interface Builder to
treat an instance variable or property
as an outlet. It’s actually defined as
nothing so it has no effect at compile
time.
Your First iOS Application - The
View Controller Interface
Declaration, Making Connections
Interface Builder User Guide -
Defining Outlets and Actions in
Xcode