Live2Enjoy7
Live2Enjoy7

Reputation: 1105

Custom Protocol not getting called in Xcode, returning 0

I've been wrestling with this for a few days - I have a custom protocol that is supposed to get the data from one of my models (via it's controller) to a view that graphs it.

Here's how I am doing this - step by step:

In graphing view, I declare the protocol as follows:

@class GraphView;

@protocol GraphViewDataSource <NSObject>

-(CGFloat)yValueForGraphView:(GraphView *)sender usingXval:(CGFloat)xVal;

@end

I then declare a property in view.h

@interface GraphView : UIView

@property (nonatomic, weak) IBOutlet id <GraphViewDataSource> dataSource;

@end

I synthesize the property in view.m:

@synthesize dataSource=_dataSource;

Then in my drawRect, I am calling this method to bring me back a CGFloat from another controller's model:

-(void) drawRect:(CGRect)rect
{
//context stuff, setting line width, etc

CGPoint startPoint=CGPointMake(5.0, 6.0);

NSLog(@"first, y value is: %f", startPoint.y);

startPoint.y=[self.dataSource yValueForGraphView:self usingXval:startPoint.x]; //accessing delegate via property

NSLog(@"now the y value now is: %f", startPoint.y);

//other code..
}

Now in my other view controller, I am importing the view.h file and declaring that it conforms to the protocol:

#import "GraphView.h"

@interface CalculatorViewController () <GraphViewDataSource>

Creating a property for the GraphView:

@property (nonatomic, strong) GraphView *theGraphView;

Synthesize:

@synthesize theGraphView=_theGraphView;

Now in the setter, I set the current controller as the dataSource (aka the delegate):

-(void) setTheGraphView:(GraphView *)theGraphView
{
    _theGraphView=theGraphView;
    self.theGraphView.dataSource=self;

}

I also set the controller as the delegate in prepareForSegue (one of the things I tried while looking for a fix):

-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"GraphViewController"])
    {
        self.theGraphView.dataSource=self;
    }
}

Finally, I implement the required method:

-(CGFloat)yValueForGraphView:(GraphView *)sender usingXval:(CGFloat)xVal
{
    CGFloat test=51.40; //just to test
    return test;
}

And the output I'm getting from my test NSLog in graphView's drawRect is:

2012-10-25 20:56:36.352 ..[2494:c07] first, y value is: 6.000000
2012-10-25 20:56:36.354 ..[2494:c07] now the y value now is: 0.000000

This should be returning 51.40 via the dataSource, but it is not. I can't figure out why! Driving me crazy, it seems like I've done everything right. But the delegate method is not getting called.

Is there some silly thing I am missing?


Additional info - diagram of controllers & GraphView:

GraphingCalc

Upvotes: 1

Views: 601

Answers (3)

Live2Enjoy7
Live2Enjoy7

Reputation: 1105

here is the final solution:

the GraphViewController had to be set the delegate of the GraphView first. That is because graphView is connected to the GraphViewController (as a custom UIView) and that particular instance of graphView is not available could not be set from other classes, I found.

The way to get information to a custom view (graphView in my case) is by setting up another protocol in the custom view's controller. Then setting another class as the delegate of that controller and passing the data to the controller first, and then to the custom view.

One very important thing is when working with storyboards, the delegate for the controller should be set in prepareForSegue (as mentioned by reckerbh). In my case, CalculatorViewController segued to the GraphViewController so my perpareForSegue looked like this:

-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"ShowGraph"])
    {
        GraphViewController *gvc=[segue destinationViewController];
        gvc.yValueSource=self;
    }
}

Note here that I am creating a new pointer to the existing instance of GraphViewController via [segue destinationViewController]. I am not creating a new one via alloc init because that would not be connected to the one I already had and would be completely irrelevant.

So I guess the main advice here is to make sure that you are using the same instance when you are setting the delegate. And if you can't access that instance, use double delegation (or even tripple I guess if you had to).

Upvotes: 0

8vius
8vius

Reputation: 5836

The way I solved this was chaining some delegates, my GraphView declares a protocol GraphViewDelegate with the following methods:

-(float)ordinateForAbscissa:(float)abscissa forGraph:(GraphView *)requestor;
-(float)scaleForGraph(GraphView *)requestor;

This protocol is implemented by the GraphViewController and he sets himself as a delegate of the GraphView when the controller loads in viewDidLoad.

Now I have another delegate that is CalculatorControllerDelegate that implements yCoordinateForX that is called from ordinateForAbscissa. CalculatorViewController implements this protocol and when segueing sets itself up as the GraphViewController's delegate.

Hope this helps.

Upvotes: 0

rickerbh
rickerbh

Reputation: 9911

The diagram helps!

What you need to do is have a method on your GraphViewController that either exposes your custom GraphView, or wraps setting the delegate in it. On the segue, you'll need to get access to the destination view controller (the GraphViewController), and then either set the delegate directly in your GraphView or pass self into the GraphViewController and have it set the delegate.

Setting the delegate in the GraphView owned by your CalculatorViewController won't impact the GraphView in your GraphViewController unless the GraphView is a singleton, or is passed into the GraphViewController.

Assuming your GraphViewController exposes a property that is the GraphView it contains, the segue in the CalculatorViewController should be something like...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Ensure the identifier string matches the segue name from the Storyboard
    if ([segue.identifier isEqualToString:@"GraphViewControllerSegue"])
    {
        // Change this to whatever class is the destination class of the segue
        GraphViewController *graphViewController = (GraphViewController *)[segue destinationViewController];
        self.graphView.delegate = self;
        // Pass the GraphView with the delegate set into the GraphViewController
        graphViewController.graphView = self.graphView;
    }
}

The fact that it's a direct segue from the button vs an action is irrelevant. If you've given the segue an identifier in the Storyboard/XIB then prepareForSegue:sender: is called, and we can determine what segue is being called.

And if you don't have a public property in your GraphViewController for your GraphView...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"GraphViewControllerSegue"])
    {
        // Change this to whatever class is the destination class of the segue
        GraphViewController *graphViewController = (GraphViewController *)[segue destinationViewController];
        [graphViewController setGraphViewDelegate:self];
    }
}

and you'll need the following in your GraphViewController implementation.

- (void)setGraphViewDelegate:(id)graphViewDelegate {
    // Set the delegate directly in the ivar
    _graphView.delegate = graphViewDelegate;
}

Upvotes: 1

Related Questions