My code works, but I don't understand who or where is calling the setter for my view's datasource delegate. I understand why calling that code makes everything work, I'd just like to know who makes the call / where it happens. The header for the view looks like this, with the last line of code being the important one:
#class GraphView;
#protocol GraphViewDataSource
-(float)YValueforXValue:(float)xValue;
#end
#interface GraphView : UIView
#property (nonatomic) CGFloat scale;
#property (nonatomic) CGPoint graphOrigin;
#property (nonatomic, weak) IBOutlet id <GraphViewDataSource> dataSource;
#end
And the view controller that conforms to the protocol:
#interface GraphViewController () <GraphViewDataSource>
#property (nonatomic, weak) IBOutlet GraphView *graphview;
#end
#implementation GraphViewController
#synthesize graphview = _graphview;
#synthesize program = _program;
-(void)setGraphview:(GraphView *)graphview {
_graphview = graphview;
self.graphview.dataSource = self;
}
I have excluded the required protocol method and more, as its not relevant. What I want to know is who calls the above setGraphView method. Unfortunately, I couldn't get help from a breakpoint (aside from knowing it was being called).
Also, that delegate first gets referenced by this code in the view:
for (CGFloat thisPointViewXValue=self.bounds.origin.x; thisPointViewXValue<=self.bounds.size.width; thisPointViewXValue +=1/self.contentScaleFactor)
{
if (FirstPoint) {
CGFloat firstpointGraphXValue = [self convertViewXValueToGraphXValue:thisPointViewXValue];
CGFloat firstpointGraphYValue = [self.dataSource YValueforXValue:firstpointGraphXValue];
CGFloat firstpointViewYValue = [self convertGraphYValueToViewY:firstpointGraphYValue];
CGContextMoveToPoint(context, thisPointViewXValue, firstpointViewYValue);
FirstPoint = NO;
}
CGFloat thisPointGraphXValue = [self convertViewXValueToGraphXValue:thisPointViewXValue];
CGFloat thisPointGraphYValue = [self.dataSource YValueforXValue:thisPointGraphXValue];
CGFloat thisPointViewYValue = [self convertGraphYValueToViewY:thisPointGraphYValue];
CGContextAddLineToPoint(context, thisPointViewXValue, thisPointViewYValue);
}
Is that where it happens???
The graphView and dataSource iVars are marked as IBOutlets, i.e. Interface Builder Outlet.
This would normally indicate that GraphViewController is loaded via a nib/xib file, and inside that nib file there are connections to these iVar from other objects inside the nib file.
So it is the nib loading mechanism that is calling the setters on those iVars.
there self.graphview.dataSource = self;
Related
I have a UISlider placed in my main view and I have added a subview UIView (via interface builder) that I have associated with its own class SecondView. I need to pass the value of the slider to my sub view to move a point in the sub view when the slider changes.
I have made changes to the original code and the the below paragraph no longer is accurate. I used used the suggested changes offered by #MatthiasBauch.
I thought it would be simple a matter of sharing an iVar between the two. I created an iVar myPoint using #property (if this is still considered an iVar) in my ViewController interface, set myPoint = sliderValue.value in my ViewController implementation in the IBAction for when the slider value changes. Then in my SecondView implementation I #import "ViewController.h" then call my call my iVar in my SecondView implementation but the way I have done it it only returns nil or 0 instead of the slider value.
I don't want to use global variables.
I have looked at other post that seem to be asking a similar question but I'm still missing the concept, I guess. My code is below.
ViewController.h
#import <UIKit/UIKit.h>
#import "SecondView.h"
#interface ViewController : UIViewController
{
SecondView *secondView; // Do I need this with secondView declared as a #property below?
}
#property (nonatomic, retain) SecondView *secondView;
- (IBAction)sliderChanged:(id)sender;
#property (weak, nonatomic) IBOutlet UISlider *sliderValue;
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
#property (weak, nonatomic) IBOutlet UIView *myView;
#end
ViewController.m
#import "ViewController.h"
#import "SecondView.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize sliderValue, myLabel, myView, secondView;
- (void)viewDidLoad
{
[super viewDidLoad];
secondView.thePoint = 50;
NSLog(#"%f", secondView.thePoint); // This is retuning a zero
}
- (IBAction)sliderChanged:(id)sender
{
secondView.thePoint = sliderValue.value;
myLabel.text = [NSString stringWithFormat:#"%.2f", sliderValue.value];
[secondView setNeedsDisplay];
}
#end
SecondView.h
#import <UIKit/UIKit.h>
#interface SecondView : UIView
#property (assign, nonatomic) CGFloat thePoint;
#end
SecondView.m
#import "SecondView.h"
#implementation SecondView
#synthesize thePoint;
- (void)drawRect:(CGRect)rect
{
float aPoint = thePoint;
NSLog(#"%f", aPoint); // this is retuning 0.000000
UIBezierPath *point = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(aPoint, 100, 4, 4)];
[point fill];
}
#end
Tell the view where to draw and don't ask the (newly allocated) viewController where to draw.
You are allocating a new viewController, this viewController of course does not know the value you set in the viewController where you actually changed the slider.
This won't work. Those two viewControllers are not the same instance. They share the same class, but that's it. Values from one viewController instance don't magically appear in a second instance.
Instead you should set a point property of the secondary view in your slider action.
Something like this:
Add a float #property to your view
#interface SecondView : UIView
#property (assign, nonatomic) CGFloat point;
#end
to draw use that point and not the initial value from a new ViewController instance.
- (void)drawRect:(CGRect)rect
{
float aPoint = self.point;
UIBezierPath *point = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(aPoint, 100, 4, 4)];
[point fill];
}
and set the point property of the secondary view in the slider action
- (IBAction)sliderChanged:(id)sender
{
secondView.point = sliderValue.value;
[secondView setNeedsDisplay]; // you might be able to omit that
myLabel.text = [NSString stringWithFormat:#"%.2f", myPoint];
}
Your ViewController has a property called #myView which points to the instance of SecondView created by the runtime.
So, your ViewController's implementation could call the drawRect: method on myView and pass in the relevant coordinates.
Here's some pseudo code for your ViewController.
- (IBAction)sliderChanged:(id)sender
{
myPoint = sliderValue.value;
myLabel.text = [NSString stringWithFormat:#"%.2f", myPoint];
[myView drawRect:CGRectMake(myPoint, 100, 4, 4)];
}
Note however that in your ViewController, myView is just a UIView, not a SecondView. You have a few choices on how to resolve this. The cleanest way is probably to remove the #import ViewController.h from SecondView and instead do the opposite. That is, have ViewController do an #import SecondView.h, then promote myView to a SecondView.
If that doesn't work for some reason (for example, if XCode demands that it remain strictly a UIView), you can still upcast the myView property at runtime like this:
[(*SecondView)myView drawRect:CGRectMake(myPoint, 100, 4, 4)];
Alternatively, you could point your slider's action at myView like the answer to this question does for a button: subclass UIView actions in viewcontroller
As usual in programming, there are several possible solutions. It's up to you to pick the best one for you. Good luck!
I have a Subclass of NSObject in which I want to call IMMEDIATELY (a sort of -(void)viewDidLoad) a method (in this case to load a MkMapView): what's the better way to do this? I think I cant use viewDidLoad, so can I use performSelector?
SubClass.h
#interface Mysubclass : NSObject <MKMapViewDelegate> {
}
SubClass.m (1st alternative)
-(id)init{
self = [super init];
if ( self != nil ) {
// THE CODE TO INITIALIZE MKMAPVIEW
}
return self
}
OR
SubClass.m (2nd alternative)
-(id)init{
[self performSelector:#selector(myMethod)];
return self;
}
-myMethod{
// THE CODE TO INITIALIZE MKMAPVIEW
}
What's the better (or correct) alternative? Its possible to avoid -(id)init? Or everytime I add a subclass, to call a method I have to write it into -(id)init? Thank you!
There is no reason to use -performSelector: in this context. If you want to add a method that initializes the MKMapView when your object is created, call the method from within the if (self) block:
- (id)init
{
self = [super init];
if (self) {
[self setupMapView];
}
return self;
}
- (void)setupMapView
{
// THE CODE TO INITIALIZE MKMAPVIEW
}
It is a matter of personal preference/style whether to have a second method -setupMapView or to simply leave the code for setting up the MKMapView in the if (self) block of the -init method or to break the code off into a second method -setupMapView called from -init.
That being said, it sounds like other things may be off with your setup. Your MKMapView should [most likely] be within a UIViewController subclass (which will probably have an associated XIB), so you will be have access to -viewDidLoad. Note that your UIViewController subclass will serve as the delegate to your MKMapView.
Update 1
In your UIViewController subclass instance (I'll assume you called it ViewController, you should have an IBOutlet to an MKMapView object. Do this in ViewController.h either by (1) adding an instance variable
#interface ViewController : UIViewController
{
IBOutlet MKMapView *myMap;
}
#end
or by (2) adding a property
#interface ViewController : UIViewController
#property (nonatomic, strong, readwrite) IBOutlet MKMapView *myMap;
#end
Now open ViewController.xib in Interface Builder. You should have an MKMapView inside the view. If you don't already, add one from the Object Library. Right click on File's Owner. Locate the row with the item myMap. Drag from the circle on the right end of the row to the MKMapView in the visible view.
Your ViewController class now has an outlet to the MKMapView. You will be able to send messages to the MKMapView subview of your view controllers view after it has been loaded.
You should have a property or an instance variable for your SubClass instance so that it doesn't get destroyed as soon as -viewDidLoad returns. Do this again by either adding an instance variable to ViewController.h
#interface ViewController : UIViewController
{
IBOutlet MKMapView *myMap;
SubClass *istance;
}
#end
or by adding a property
#interface ViewController : UIViewController
#property (nonatomic, strong, readwrite) IBOutlet MKMapView *myMap;
#property (nonatomic, strong, readwrite) SubClass *istance;
#end
Now, in ViewController.m, you need to define -viewDidLoad so that self.istance is set as the delegate of self.myMap. In the comments, I had suggested creating your own initializer -initWithMapView:. If you plan on having SubClass do some extensive set-up of your MKMapView, that makes sense. If you just want SubClass to be the delegate of the MKMapView, there's no need for such a method.
Let's consider both cases:
(1) using a method -[SubClass initWithMapView:]:
In ViewController.m you'll have (within the #implementation of ViewController)
- (void)viewDidLoad
{
self.istance = [[SubClass alloc] initWithMapView:self.myMap];
}
In SubClass.h you'll have (within the #interface of SubClass)
- (id)initWithMapView:(MKMapView *)mapView;
#property (nonatomic, weak, readwrite) MKMapView *mapView;
In SubClass.m you'll have (within the #implementation of SubClass)
- (id)initWithMapView:(MKMapView *)mapView
{
self = [super init];
if (self) {
self.mapView = mapView;
self.mapView.delegate = self;
//more setup of mapView.
}
return self;
}
(2) using -[SubClass init]:
Instead, in ViewController.m you'll have
- (void)viewDidLoad
{
self.istance = [[SubClass alloc] init];
self.myMap.delegate = self.istance;
}
I have classes
*ColorViewController
*DrawRectangle
In ColorViewController.h:
#import "DrawRectangle.h"
#interface ColorViewController : UIViewController {
DrawRectangle *DrawRectangle;
}
#property (nonatomic,retain) DrawRectangle *DrawRectangle;
And in DrawRectangle.h:
#interface DrawRectangle : UIView {
CGFloat redc;
}
#property (nonatomic) CGFloat redc;
I have also added the DrawRectangle class in ViewController.xib to one of UIView.
I want to change the value of CGFloat redc from file ColorViewController.m
I'm doing it this way:
- (void)viewDidLoad
{
CGFloat myfloat = 1.0;
self.DrawRectangle.redc = myfloat;
I'm calling NSLog with this 'redc' in DrawRectangle, but it says 0.0000000 each time.
How can I replace the value of 'redc' from ViewController? I don't think I need to use NSNotification, there must be easier way if I have the DrawRectangle loaded into ViewController.
Looks like your object hasn't been instantiated yet. Try:
-(void)viewDidLoad {
[super viewDidLoad];
CGFloat myfloat = 1.0;
[drawRectangle setRedc:myfloat];
}
You should also avoid naming your objects the same name as the class as a general programming practice. I Would suggest renaming the object to drawRectangle.
You also need to hook up your drawRectangle object in your xib
#property (nonatomic, retain) IBOutlet DrawRectangle *drawRectangle;
New to iOS/Objective-C here. I've spent a lot of time trying to figure this out, but just can't manage it. Here's what's happening:
Click a 'Graph' button on my RootViewController's view
GraphViewController takes over, has a graphView property
I set the graphView's dataSource property to self
Checking self.graphView.dataSource with NSLog confirms that it does indeed point to self
When GraphView's drawRect: is called, self.dataSource is set to (null), where I expected it to point to the GraphViewController object
To summarise: I'm instantiating the graphView property in the view controller, then setting its dataSource, but by the time the view's drawRect: is called the dataSource is no longer set.
GraphViewController.m:
#import "GraphViewController.h"
#import "GraphView.h"
#implementation GraphViewController
#synthesize graphView = _graphView;
#synthesize program = _program;
- (GraphView *)graphView {
if(!_graphView) {
_graphView = [[GraphView alloc] init];
[_graphView setDataSource:(id)self];
}
return _graphView;
}
#end
GraphViewController.h:
#import <UIKit/UIKit.h>
#import "GraphView.h"
#interface GraphViewController : UIViewController
#property (strong, nonatomic) IBOutlet GraphView *graphView;
#end
GraphView.m:
#import "GraphView.h"
#implementation GraphView
#synthesize dataSource = _dataSource;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSLog(#"initWithFrame called, self.dataSource=%#", self.dataSource);
}
return self;
}
- (void)drawRect:(CGRect)rect
{
NSLog(#"drawRect:");
NSLog(#"\tself=%#", self);
NSLog(#"\tself.dataSource=%#", self.dataSource); // is (null), shouldn't be
[self.dataSource programToGraph];
}
#end
GraphView.h:
#import <UIKit/UIKit.h>
#class GraphView;
#protocol GraphViewDataSource
- (id)programToGraph;
#end
#interface GraphView : UIView
#property (nonatomic, weak) IBOutlet id <GraphViewDataSource> dataSource;
#end
So it looks like the GraphViewController instance is being deallocated, which will nil out the dataSource property.
So you should go back and look at how you are creating and managing that GraphViewController. A common mistake is to create a view controller like that, then borrow its view and throw it into some other view controller's view hierarchy, and then let the original view controller just go away.
I would look at that, and if you don't see it there, post the code where you create and present the GraphViewController.
Oh, and where are you calling initWithFrame: from?? It almost looks like you could also have two different instances of GraphView floating around. Try logging 'self' along with self.datasource to check that also.
I've got the following method on a GameScreen.m file, with its own declaration - (void) drawNumbers on a GameScreen.h file:
//GameScreen.h
#import <UIKit/UIKit.h>
#interface GameScreen : UIView
{
IBOutlet UIButton *cell00;
}
- (void) drawNumbers;
- (IBAction) onCellClick:(id)sender;
#property (nonatomic, retain) IBOutlet UIButton *cell00;
#end
//GameScreen.m
#import "GameScreen.h"
- (void) drawNumbers
{
//testing if this works, so far it doesn't
[cell00 setTitle:#"Whatever" forState:UIControlStateNormal];
[cell00 setTitle:#"Whatever" forState:UIControlStateHighlighted];
}
I'm trying to call this method from my GameScreenViewController.m file, this way:
//GameScreenViewController.m
#import "GameScreenViewController.h"
#import "GameScreen.h"
...
- (void) viewDidLoad
{
GameScreen *aGameScreen = [[GameScreen alloc] init];
[aGameScreen drawNumbers];
[aGameScreen release];
[super viewDidLoad];
}
This is supposed to change the title of a button in a GameScreen.xib file where GameScreenViewController.m is the viewController and GameScreen class is the event handler where I get all the button clicks, timers running, etc. I am trying to call [drawNumbers] from [viewDidLoad] since I want the title to be changed when the screen is brought up front (screen management is done through the AppDelegate files).
The thing is, if I call drawNumbers instance from inside the same class through
//GameScreen.m
#import GameScreen.h
-(void) onButtonClick:(id)sender
{
//some other code
[self drawNumbers];
}
it works (as to say, nothing wrong with the code implementation or the graphic interface).
I've browsed through Apple Guide and tons of pages on the Internet, but I can't seem to find any light to this. Any further help (including answers as to where exactly find the answer in the ADG) would be really appreciated.
(Edited: here goes the AppDelegate code to flip to the specific view, just in case):
//myAppAppDelegate.h
#import <UIKit/UIKit.h>
#class myAppViewController, GameScreenViewController;
#interface myAppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
myAppViewController *viewController;
GameScreenViewController *gameScreenViewController;
}
- (void) flipToGameScreen;
#property (nonatomic, retain) UIWindow *window;
#property (nonatomic, retain) GameScreenViewController *gameScreenViewController;
#end
//myAppAppDelegate.m
-(void) flipToGameScreen
{
GameScreenViewController *aGameScreenView = [[GameScreenViewController alloc] initWithNibName: #"GameScreen" bundle:nil];
[self setGameScreenViewController:aGameScreenView];
[aGameScreenView release];
[gameScreenViewController.view.frame = [[UIScreen mainScreen] applicationFrame];
[viewController.view removeFromSuperview];
[self.window addSubview:[gameScreenViewController view]];
}
Since your cell00 is to be set by a NIB it will be nil if you simply do [[GameScreen alloc] init]. It will only be set if the corresponding NIB is loaded (and a connection is actually set up).
If the cell can be accessed in your viewDidLoad, create a property on GameScreen and pass it through the property (or a dedicated initWithCell: or something).
If you have something like an IBOutlet GameScreen *aGameScreen; on your GameScreenViewController (and also established a connection to cell00 in the same NIB) you should access that instead.