Objective-C – Retained property after being set is nil? - objective-c

I have two classes:
A UIViewController and a class that's subclassing NSObject that acts as a downloading helper class called OfficesParser. OfficesParser is using ASIHTTPRequest and I set the delegate for the download requests to be my UIViewController.
EDIT: Interface for the UIViewController:
#interface OfficesViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, ASIHTTPRequestDelegate> {
OfficesParser *officesParser;
}
#property (nonatomic, retain) OfficesParser *officesParser;
#end
In the UIViewController implementation I set up the OfficesParser like so:
- (void)viewDidLoad {
[super viewDidLoad];
self.officesParser = [[[OfficesParser alloc] init] autorelease]; // self.officesParser is retained
}
Then before the view appears I call my my OfficesParser object to download some data for me:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.officesParser doNetworkOperations];
}
Also in my UIViewController I have setup the appropriate delegate methods to deal with the data after it has been downloaded. In particular I'm interested in this delegate method that will run after all the data has been processed in my download queue. I can see that the delegate method is running from the log. But for some reason self.officesParser in here is nil.
- (void)queueFinished:(ASINetworkQueue *)queue {
DLog(#"queueFinished running");
[self.officesParser test]; // test will not get called because self.officesParser is nil
}

Related

Modifying string content in NSTextView works under viewDidLoad method, but not under myMethod

I am trying to update the contents of an NSTextView that is connected to myViewController as a referencing outlet to the Files Owner which is the subclass myViewController.
When I use an IBAction from a button, or use the viewDidLoad method of the controller, I can update the text fine. However, when I try run the method from another class (referred to in this example as anotherViewController), it runs the method, but the textview does not change.
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
self.outText.string = #"I am updated text! I also work!";
}
- (void)updateTextView:(NSString *)argText {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
- (void)updateTextViewWithoutArg {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
#end
In anotherViewController.m , which has all the relevant imports, I call this:
myViewController *viewtask = [[myViewController alloc] init];
[viewtask updateTextViewWithoutArg];
Nothing happens. The method runs and logs that it should have updated, but no text updates. I have tried many different approaches, including textstorage and scrollrange methods, they all work the already working sections, but make no difference in the sections not working.
I've also tried just for fun:
myViewController *viewtask;
[viewtask updateTextViewWithoutArg];
Also using the instance variable _outText
Also using [self.outText setString:#"string"];
Also using [_outText setString:#"string"];
Again, they work but only in the already working sections.
This should be simple but isn't logical to me. In swift all I need to do is
self.outText.string = "I update whenever I'm called!"
Views you create in Interface Builder are lazily created, so if you access them before viewDidLoad is called they are nil.
If your case, calling
myViewController *viewtask = [[myViewController alloc] init];
does not cause the views to be created so when you call
[viewtask updateTextViewWithoutArg];
self.outText is nil.
You can see that this is what is happening by updating your code as below:
- (void)updateTextView:(NSString *)argText {
NSAssert(self.outText != nil, #"self.outText must not be nil");
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
you should see the assert fire.
I appear to have found a solution by making myViewController a singleton class and using sharedInstance. For this particlar app, myViewController is a debug output window and will never need to be placed in another view.
I won't accept this answer yet, as it's not the best one I'm sure. There may still be a proper solution presented that allows finding the applicable myViewController instance, and modifying the outText property attached to it. Using this singleton makes subclassing tedious as I would have to make a new class for every instance if I wanted to be able to address say 10 View Controllers.
Anyway - the way I've been able to satisfy my simple requirement:
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
+ (id)sharedInstance;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
static myViewController *sharedInstance = nil;
+ (myViewController *)sharedInstance {
static myViewController *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[myViewController alloc] init];
});
return sharedInstance;
}
- (void)viewDidLoad {
[super viewDidLoad];
sharedInstance = self;
}
- (void)viewDidUnload {
sharedInstance = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
sharedInstance.outText.string = #"Button Pressed";
}
- (void)updateTextView:(NSString *)argText {
sharedInstance.outText.string = argText;
}
- (void)updateTextViewWithoutArg {
sharedInstance.outText.string = #"I make it to the TextView now";
}
#end
Now when I use this code from within anotherViewController.m it updates the right instance:
[myViewController.sharedInstance updateTextView:#"Updating with this string"];

delegation and passing data back from childViewController

I have been struggling with this for a few days and have received valuable help on the way from S.O. I have made the simplest possible project to reduce the possibilities of it being a typo.
All my project is, is a ViewController that holds a container view hooked to a childViewController. The "parent" ViewController is set as the delegate of the childViewController. In the viewDidLoad of the child I am passing a value which is just a string. This string should be passed on to the parent and printed on the console. Here are the files.
ViewController.h
#import <UIKit/UIKit.h>
#import "ChildViewController.h"
#interface ViewController : UIViewController <ChildViewControllerDelegate>
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#property NSString *myValueRetrieved;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
ChildViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildVC"];
controller.delegate = self;
NSLog(#"Here is my value: %#",self.myValueRetrieved);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void) passValue:(NSString *)theValue{
self.myValueRetrieved = theValue;
}
#end
ChildViewController.h
#import <UIKit/UIKit.h>
#protocol ChildViewControllerDelegate;
#interface ChildViewController : UIViewController
#property (weak)id <ChildViewControllerDelegate> delegate;
#end
#protocol ChildViewControllerDelegate <NSObject>
- (void) passValue:(NSString*) theValue;
#end
ChildViewController.m
#import "ChildViewController.h"
#interface ChildViewController ()
#property NSArray *colors;
#end
#implementation ChildViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.delegate passValue:#"Hello"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#end
Am I right to think that when the app is launched, the console should log the following message: "here is my value: hello". Am I doing something wrong in terms of logically not getting delegation or is it just a silly typo somewhere? tx
You're assuming that the view is loaded when the view controller is instantiated. That's now how it works. The view gets loaded when it's needed (like to add to the parent view).
But you can force the view to load and make this work. Call -loadViewIfNeeded on the child view controller right after setting the delegate. That will probably get you what you want:
controller.delegate = self;
[controller loadViewIfNeeded];
NSLog(#"Here is my value: %#",self.myValueRetrieved);
Or, if you do want to call back the delegate in viewDidLoad, then you'd need to move the NSLog to the -passValue: method, since the primary view controller's viewDidLoad method will have already finished running.
To do this make ParentController a delegate of ChildController. This allows ChildController to send a message back to ParentController enabling us to send data back.
For ParentController to be delegate of ChildController it must conform to ChildController's protocol which we have to specify. This tells ParentController which methods it must implement.
In ChildController.h, below the #import, but above #interface you specify the protocol.
#class ChildController;
#protocol ViewControllerBDelegate <NSObject>
- (void)addItemViewController:(ChildController *)controller didFinishEnteringItem:(NSString *)item;
#end
next still in the ChildController.h you need to setup a delegate property and synthesize in ChildController.h
#property (nonatomic, weak) id <ChildControllerDelegate> delegate;
In ChildController we call a message on the delegate when we pop the view controller.
NSString *itemToPassBack = #"Pass this value back to ParentController";
[self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
That's it for ChildController. Now in ParentController.h, tell ParentViewController to import Child and conform to its protocol.
import "ChildController.h"
#interface ParentController : UIViewController
In ParentController.m implement the following method from our protocol
- (void)addItemViewController:(ChildController *)controller didFinishEnteringItem:(NSString *)item
{
NSLog(#"This was returned from ChildController %#",item);
}
The last thing we need to do is tell ChildController that ParentController is its delegate before we push ChildController on to nav stack.
ChildController *ChildController = [[ChildController alloc] initWithNib:#"ChildController" bundle:nil];
ChildController.delegate = self
[[self navigationController] pushViewController:ChildController animated:YES];

implemented delegate method but told it's not implemented

I'm trying to implement delegation. In the .h file of a custom class , I do this
#import <UIKit/UIKit.h>
#class Timer;
#protocol TimerDelegate
-(void)myClassDelegateMethod:(Timer *)timer;
#end
typedef void(^MyCustomBlock)(void);
#interface Timer : UILabel
#property (nonatomic, weak) id <TimerDelegate> delegate;
In the .m file I synthesize the delegate and also called the delegate method, checking to see first if the delegate implements the method
#synthesize delegate;
-(void)countdownTime:(NSTimer *)timer
{
NSLog(#"countdownTime called");
....
[self.delegate myClassDelegateMethod:self];
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(myClassDelegateMethod:)]) {
[self.delegate performSelector:#selector(myClassDelegateMethod:)];
} else {
NSLog(#"Delegate doesn't implement myClassDelegateMethod");
}
when I run my code, I'm told the delegate doesn't implement the method. Here's how I implement it
In the viewController, I declare that it conforms to the protocol
#interface scViewController : UIViewController <TimerDelegate>
And then in the .m file of the viewController, I implement the delegate's method
- (void) myClassDelegateMethod:(Timer *) sender {
NSLog(#"Delegates are great!");
}
Can you explain how I've failed to implement the delegate method properly?
Update, in the viewController, I have a method that creates timer instances
-(Timer *)timer
{
_timer = [[Timer alloc] init];
return _timer;
}
In viewDidLoad, I do this
self.timer.delegate = self;
Your timer method is a problem. It should be:
-(Timer *)timer
{
if (!_timer) {
_timer = [[Timer alloc] init];
}
return _timer;
}
As you have it, every time you do self.timer you were creating a new timer so the delegate was only applied to one of the many instances.

delegate method not getting called

I have looked at all the other questions with the same problem but I cannot seem to get my heard around it. I am pretty sure I have done everything correctly as this is not my first time using delegates.
//PDFView.h
#class PDFView;
#protocol PDFViewDelegate <NSObject>
-(void)trialwithPOints:(PDFView*)pdfview;
#end
#interface PDFView : UIView
#property (nonatomic, weak) id <PDFViewDelegate> delegate;
In the implementation file i am trying to call the delegate method from touchesMoved delegate of view
//PDFView.m
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.delegate trialwithPOints:self];
}
The class where the delegate method is implemented
//points.h
#import "PDFView.h"
#interface points : NSObject <PDFViewDelegate>
//points.m
//this is where the delegate is set
- (id)init
{
if ((self = [super init]))
{
pdfView = [[PDFView alloc]init];
pdfView.delegate = self;
}
return self;
}
-(void)trialwithPOints:(PDFView *)pdf
{
NSLog(#"THE DELEGATE METHOD CALLED TO PASS THE POINTS TO THE CLIENT");
}
So this is how i have written my delegate and somehow the delegate is nil and the delegate method is never called.
At the moment I am not doing anything with the delegate, I just want to see it working.
Any advices on this would be highly appreciated.
I think it is because you did not hold the reference to the instance of the delegate, and it got released because it is declared weak. You might be doing this:
pdfView.delegate = [[points alloc] init];
which you should fix to something like:
_points = [[points alloc] init];
pdfView.delegate = _points;
where _points is instance variable.

Refer to a main view controller property by another class

I work on a project for iPad with Xcode 4.
I have a main view controller with many UITextField.
The TextFieldDelegate is a separate class in a separate file.
How can I refer, from TextFieldDelegate to a property (to a UITextField) of the main view controller (for example assign a value to a double)?
Thank you.
In most cases, if you want to use a separate delegate you should not need more information than what is passed to the delegate (the method's parameters). However, if you don't want to use your MainViewController as a delegate for your UITextField, you can initialize your TextFieldDelegate in your MainViewController instance and pass it the MainViewController instance.
For example you could have:
#import "MainViewController.h"
#interface TextFieldDelegate<UITextFieldDelegate> {
MainViewController* mainViewController;
}
#property(nonatomic,retain) MainViewController* mainViewController;
-(id)initWithController:(MainViewController*)controller;
#end
#implementation TextFieldDelegate
#synthesize mainViewController;
-(id)initWithController:(MainViewController*)controller {
if(self = [super init]) {
//some stuff
self.mainViewController = controller;
}
return self;
}
#end
Then in your MainViewController:
TextFieldDelegate tfd = [[TextFieldDelegate alloc] initWithController:self];
You just need to set the TextFields' delegate to tfd and you should be able to reference the MainViewController properties from the TextFieldDelegate instance. It's also possible to initiate it somewhere else, as long as you send the MainViewController instance to your TextFieldDelegate instance.
Edit: woups forgot a few '*'