Protocol Delegate doesn't get called - objective-c

Hi Im trying to download a Photo from the internet using GCD. After the image is downloaded I call the delegate to let the class know that the image is downloaded.
Here is the code I use to download the image
-(void)getVenuePhotos:(NSString *)venueID
{
dispatch_queue_t getVenuePhotos = dispatch_queue_create ( "getVenuePhotos" , NULL );
dispatch_async (getVenuePhotos, ^{
//Download image, Lots of code not displaying
UIImage *image = [[UIImage alloc] initWithData:photoData];
dispatch_async(dispatch_get_main_queue(),^{
//This delegate doesn't get called
[self.delegate gotVenuePhotos:image];
});
});
dispatch_release (getVenuePhotos);
}
Here is how I implemented my Protocol in my POIPhoto.h
#protocol POIPhotoProtocol <NSObject>
-(void)gotVenuePhotos:(UIImage *)image;
#end
#interface POIPhoto : NSObject
-(void)getVenuePhotos:(NSString *)venueID;
#property(nonatomic, weak) id <POIPhotoProtocol> delegate;
#end
And in my class I want the call back
#interface PoiDetailViewController : UIViewController <POIPhotoProtocol>
And finaly I implement the protocol method
-(void)gotVenuePhotos:(UIImage *)image
{
self.venuePhoto.image = image;
}
The problem is that the gotVenuePhotos never gets called even though in my image downloader the [self.delegate gotVenuePhotos:image]; gets called

The issue is happening because you are not setting the delegate.
In your viewDidLoad set the delegate like:
self.delegate = self;

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"];

Objective-c proper delegation

I'm new to objective-c and, maybe I haven't grassped the concept of delegation very clearly yet, but i hope to do it by using it. I'm trying to implement a delegation in my app.
Idea is that i have class TableViewController which has NSMutableArray used for TableView initialization. I need to reinitialize this Array from my DropDown class. I'v tried to do that using delegation but failed to do it yet, maybe there is something wrong with it. I could pass TableViewController to DropDown class and edit the table via object. But i'd like to get it done using delegation.
Here is my TableViewController.h
#protocol TableViewControllerdelegate;
#interface TableViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,MFMessageComposeViewControllerDelegate>
{
ControllerType controllerType;
}
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#property (retain, nonatomic) NSMutableArray *dataArray;
#property (retain, nonatomic) NSArray *imageArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andType:(ControllerType)type;
- (void)sendSMS: (NSString *) sms;
#end;
Here is my DropDown.h
#import "TableViewController.h"
#interface DropDownExample : UITableViewController <VPPDropDownDelegate, UIActionSheetDelegate> {
#private
VPPDropDown *_dropDownSelection;
VPPDropDown *_dropDownSelection1;
VPPDropDown *_dropDownSelection2;
VPPDropDown *_dropDownSelection3;
VPPDropDown *_dropDownSelection4;
VPPDropDown *_dropDownDisclosure;
VPPDropDown *_msg;
VPPDropDown *_dropDownCustom;
NSIndexPath *_ipToDeselect;
}
+ (bool) uncheck:(UITableViewCell *) cell andData:(NSString *) data;
- (void)reloadData;
#end
And this is how i try to edit my tableview object array
TableViewController *newControll = (TableViewController*)[UIApplication sharedApplication].delegate;
NSMutableArray *arrayWithInfo = [[NSMutableArray alloc] initWithObjects:AMLocalizedString(#"Status", nil),AMLocalizedString(#"Call", nil),AMLocalizedString(#"Location", nil),AMLocalizedString(#"Control", nil),AMLocalizedString(#"Sim", nil),AMLocalizedString(#"Object", nil),AMLocalizedString(#"Info", nil),nil];
newControll.dataArray = arrayWithInfo;
[arrayWithInfo release];
[newControll.tableView reloadData];
I get it running, but it get's '-[AppDelegate setDataArray:]: unrecognized selector sent to instance after reaching this code.
OK, I am not sure if I got this right but it finally clicked for me what delegation is and why I need it. Hopefully you'll understand too once you read through my scenario.
History
Previously, in my UITabBar app, I wanted to show a custom form view overlaid on top of my view controller to enter name and email.
Later I also needed to show the same custom overlay on top of another view controller on another tab.
At the time I didn't really know what delegation was for, so the first method I used to tackle this problem was NSNotificationCenter. I duplicated the same code to my second view controller and hooked it up to a button press event.
On pressing a button on the second view controller on another tab, it certainly showed my custom overlay, just like my first view controller.
However, this is where the problem starts.
The Problem
I needed to close my custom form view. So using NSNotificationCenter, I posted a notification and the listener callback method for the notification was told to close my custom view.
The problem was, using NSNotificationCenter, all listeners both in my first tab and my second tab responded to the posted notification and as a result, instead of closing just the custom form view overlaid on top of my second view controller, it closed ALL my custom view, regardless of where the custom view was opened from.
What I wanted was when I tap on the "X" button to close my custom form view, I only want it to close it for that single instance of the custom view, not all the other ones I had opened.
The Solution: Delegation
This is where it finally clicked for me - delegation.
With delegation, I tell each instance of my custom form view who the delegate was, and if I was to tap on the "X" button to close my custom view, it only close it for that single instance that was opened, all the other view controllers were untouched.
Some Code
Right, down to some code.
Not sure if this is the best way to do it (correct me if I am wrong) but this is how I do it:
// ------------------------------------------------------------
// Custom Form class .h file
// ------------------------------------------------------------
#protocol MyCustomFormDelegate <NSObject>
// if you don't put a #optional before any method, then they become required
// in other words, you must implement these methods
-(void)sendButtonPressed;
-(void)closeButtonPressed;
// example: these two methods here does not need to be implemented
#optional
-(void)optionalMethod1;
-(void)optioinalMethod2;
#end
#interface MyCustomFormView : UIView
{
...
id<MyCustomFormDelegate> delegate;
}
...
#property (nonatomic, retain) id<MyCustomFormDelegate> delegate;
#end
// ------------------------------------------------------------
// Custom Form class .m file
// ------------------------------------------------------------
...
#implementation TruckPickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
...
[btnSend addTarget:self selector:#selector(sendEmail) forControlEvent:UIControlEventTouchUpInside];
...
[btnClose addTarget:self selector:#selector(closeForm) forControlEvent:UIControlEventTouchUpInside];
}
return self;
}
-(void)sendEmail
{
// code sends email
...
// ------------------------------------------------------------
// tell the delegate to execute the delegate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate sendButtonPressed];
}
-(void)closeForm
{
// ------------------------------------------------------------
// tell the delegate to execute the delgate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate closeButtonPressed];
}
// ------------------------------------------------------------
// view controller .h file
// ------------------------------------------------------------
#import "MyCustomFormView.h"
// conform to our delegate protocol
#interface MyViewController <MyCustomFormDelegate>
{
...
// create a single instance of our custom view
MyCustomFormView *customForm;
}
#property (nonatomic, retain) MyCustomFormView *customForm;
// ------------------------------------------------------------
// view controller .m file
// ------------------------------------------------------------
#synthesize customForm;
-(void)viewDidLoad
{
customForm = [[MyCustomFormView alloc] initWithFrame:....];
// tell our custom form this view controller is the delegate
customForm.delegate = self;
// only show the custom form when user tap on the designated button
customForm.hidden = YES;
[self.view addSubview:customForm];
}
-(void)dealloc
{
...
[customForm release];
[super dealloc];
}
// helper method to show and hide the custom form
-(void)showForm
{
customForm.hidden = NO;
}
-(void)hideForm
{
customForm.hidden = YES;
}
// ------------------------------------------------------------
// implement the two defined required delegate methods
// ------------------------------------------------------------
-(void)sendButtonPressed
{
...
// email has been sent, do something then close
// the custom form view afterwards
...
[self hideForm];
}
-(void)closeButtonPressed
{
// Don't send email, just close the custom form view
[self hideForm];
}
You get that error, because (as the error says) you're sending a setDataArray: message to your app delegate (the AppDelegate class).
[UIApplication sharedApplication].delegate;
This will return the delegate of you app. There are a couple of ways to find out which class is your app's delegate, but usually it's called AppDelegate (as in your case) and it's implementing the UIApplicationDelegate protocol too.
You can't simply cast that to a completely different class. If your app delegate has an ivar or property of type TableViewController you have to use accessors to get it. If it's a property, you can use the dot notation. If it's an ivar, you can either implement a getter method that returns the ivar, or make it a property instead.
// assuming your app delegate has a TableViewController property called myTableViewController.
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
TableViewController *tableViewController = appDelegate.myTableViewController;
This will fix the error, but your use of the delegate pattern is wrong too. I don't see where you're using any custom delegates. You forward declare a TableViewControllerdelegate protocol, but I don't see any declaration of it, or I don't see where you're trying to use it.

How to call viewDidLoad after [self dismissModalViewControllerAnimated:YES];

Okay. If you have two viewControllers and you do a modal Segue from the first to the second, then you dismiss it with [self dismissModalViewControllerAnimated:YES]; it doesn't seem to recall viewDidLoad. I have a main page (viewController), then a options page of sorts and I want the main page to update when you change an option. This worked when I just did a two modal segues (one going forward, one going back), but that seemed unstructured and may lead to messy code in larger projects.
I have heard of push segues. Are they any better?
Thanks. I appreciate any help :).
That's because the UIViewController is already loaded in memory. You can however use viewDidAppear:.
Alternatively, you can make the pushing view controller a delegate of the pushed view controller, and notify it of the updates when the pushed controller is exiting the screen.
The latter method has the benefit of not needing to re-run the entire body of viewDidAppear:. If you're only updating a table row, for example, why re-render the whole thing?
EDIT: Just for you, here is a quick example of using delegates:
#import <Foundation/Foundation.h>
// this would be in your ModalView Controller's .h
#class ModalView;
#protocol ModalViewDelegate
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView;
#end
#interface ModalView : NSObject
#property (nonatomic, retain) id delegate;
#end
// this is in your ModalView Controller's .m
#implementation ModalView
#synthesize delegate;
- (void)didTapSaveButton
{
NSLog(#"Saving data, alerting delegate, maybe");
if( self.delegate && [self.delegate respondsToSelector:#selector(modalViewSaveButtonWasTapped:)])
{
NSLog(#"Indeed alerting delegate");
[self.delegate modalViewSaveButtonWasTapped:self];
}
}
#end
// this would be your pushing View Controller's .h
#interface ViewController : NSObject <ModalViewDelegate>
- (void)prepareForSegue;
#end;
// this would be your pushing View Controller's .m
#implementation ViewController
- (void)prepareForSegue
{
ModalView *v = [[ModalView alloc] init];
// note we tell the pushed view that the pushing view is the delegate
v.delegate = self;
// push it
// this would be called by the UI
[v didTapSaveButton];
}
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView
{
NSLog(#"In the delegate method");
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
ViewController *v = [[ViewController alloc] init];
[v prepareForSegue];
}
}
Outputs:
2012-08-30 10:55:42.061 Untitled[2239:707] Saving data, alerting delegate, maybe
2012-08-30 10:55:42.064 Untitled[2239:707] Indeed alerting delegate
2012-08-30 10:55:42.064 Untitled[2239:707] In the delegate method
Example was ran in CodeRunner for OS X, whom I have zero affiliation with.

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

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
}

objective c subclass UIImageView

I'm trying to subclass UIImageView to add some custom functionality in it. I started trying to do the basics, just init an object with a given image and display it on the screen.
Using UIImageView everything works fine, but if i change my object from UIImageView to my custom class, no image is displayed.
Here's what i've done so far:
//Imager.h
#import <UIKit/UIKit.h>
#interface Imager : UIImageView {
}
-(id) init;
-(void) retrieveImageFromUrl: (NSString *)the_URL;
#end
//Imager.m
#import "Imager.h"
#implementation Imager
#synthesize image;
-(id)init
{
self = [super init];
return self;
}
-(void) retrieveImageFromUrl: (NSString *)the_URL{
//Not implemented yet
}
#end
So, i am using this statment: cell.postImage.image = [UIImage imageNamed:#"img.png"];
Before this is executed, i also have postImage = [[UIImageView alloc] init];
If postImage is declared as UIImageView everything works as expected. But if i change it to Imager instead, then nothing is displayed.
What am i missing?
You're synthesizing image, thus blocking calls to setImage on the superclass (i.e. UIImageView) when you attempt to use the dot notation to set the image.
Remove this line:
#synthesize image;
I would suggest using an Objective-C "Category" instead of subclassing the UIImageView. As long as you don't have to add any member variables, a Category is a better solution. When you use a category you can call your extended functions on any instance of the original class (in your case UIImageView. That removes the need for you to consciously use your subclass anywhere you might want to use your new functions.
You can just do the following in a header:
#interface UIImageView (UIImageView+URL)
-(void) retrieveImageFromUrl: (NSString *)the_URL;
#end
Then in an implimentation file:
#implimentation UIImageView (UIImageView+URL)
-(void) retrieveImageFromUrl: (NSString *)the_URL
{
// implimentation
}
#end
Then wherever you want to use your new function on a UIImageView, you just need to include the header file.