Reference CustomView from my Controller - objective-c

This is driving me crazy. Very grateful if someone could help me out!
Problem:
I have subclassed NSView (and implemented initWithRect: and drawRect:) and connected it to a customView in IB. Then in my Controller.h I am trying to create a reference to this instance by using Viewer *view; (Viewer is my subclass of NSView). However, when I try to reach a dummy function that only performs printf("something") nothing happens. Since I haven't allocated any memory for this instance [view retainCount] gives 0. My understanding was that IB would instantiate this class for me. The reason that I want to be able to reference the instance is so that I can call [view setNeedsDisplay: YES] so that the view will be redrawn. I have connected my CustomView with the view outlet in IB and saved.
#import "Viewer.h"
#import "Controller.h"
#implementation Viewer
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
return self;
}
-(void)awakeFromNib
{
printf("awake!\n"); //works!
}
- (void)drawRect:(NSRect)rect
{
CGContextRef myContext = [[NSGraphicsContext currentContext]graphicsPort];
for (int i=0; i<8; i++) {
for (int j=0; j<8; j++) {
printf("%f\n",gPopulation[i][j]/2);
CGContextSetRGBFillColor (myContext, gPopulation[i][j]/2, 0.3, 0.1, 1); // Set color
CGContextFillRect (myContext, CGRectMake (i*50, j*50, 50, 50 ));
}
}
}
**- (void) redraw { //dummy function that I can't reach from controller with [view redraw]. Gives no error, but retainCount = 0**
printf("redraw------\n");
//[self display];
}
#end
#import <Cocoa/Cocoa.h>
#import "Viewer.h"
double gPopulation[8][8];
#interface Controller : NSObject {
NSMutableArray *emptySpots;
int nEmpty, nWhite, nBlack;
NSOperationQueue *queue;
IBOutlet Viewer *view;
}
- (void) main;
- (id) initWithMain;
- (void) updatePopulation;
- (void) initPopulation;
#end

The steps you describe aren't entirely clear, but here are several things that stand out:
1 - It's not your place to ask an object for its -retainCount to determine whether it's being used or not. You have no way of knowing (nor are you supposed to know or depend upon knowing) what else might have an interest in this object.
2 - You check for a valid object by seeing if the object pointer ("view" in your case) is valid (points to an object) or is nil.
3 - When creating a custom NSView subclass and instantiating a copy within your nib/xib, you need to drag an NSView instance out from the library, then set its class name to that of your subclass, otherwise Interface Builder is just creating an instance of NSView. I don't think this is your problem (see #4) but you didn't say this so it's another thing to check.
4 - When you send a message to nil, nothing is exactly what is supposed to happen, so it's likely your "view" pointer/outlet is nil.
5 - It's easy to confuse "an instance I created and referenced in a nib/xib" with "an instance I created at runtime". This happens frequently with those new to Cocoa. Are you absolutely positive that the instance of the object that holds the connection (named "view") is the same as the instance you're examining at runtime? For example, you create a controller class named MyController, instantiated it in your nib/xib (as a blue cube), wired it up, etc. Then at runtime, you instantiate a new MyController ([[MyController alloc] init]...) and tried to access its (nil) "view" outlet, which points to nothing because it's not the same instance as that in your nib/xib.

Related

Cocoa Subclassing weirdness

I'm trying to understand how Subclassing works in Cocoa.
I've created a new Cocoa Application Project in XCode 5.1.
I drag a new Custom View onto the main window.
I create a new Objective-C class CustomViewClass and set it as a Subclass of NSView. This generates the following :
CustomViewClass.h
#import <Cocoa/Cocoa.h>
#interface CustomViewClass : NSView
#end
CustomViewClass.m
#import "CustomViewClass.h"
#implementation CustomViewClass
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
NSLog(#"Custom View initialised");
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
#end
Note that I added the NSLog(#"Custom View initialised"); line so I can track what is going on.
In interface Builder, I select the Custom View and within the Idenditiy Inspecter set it's custom Class to CustomView. Then I run the Application.
As expected I get a Custom View initialised message in the Console.
I do exactly the same with an NSTextField adding it to the window, creating a new class TextFieldClass and the NSTextField custom Class is to TextFieldClass. I also add a NSLog(#"Text Field initialised"); in the same place as above to track things.
However when I run the App, I only get the Custom View initialised message in the Console and not the NSLog(#"Text Field initialised");message.
So initially I think that NSTextField doesn't recieve the initWithFrame message when it is created. So I add an initialiser to TextFieldClass :
- (id)init {
self = [super init];
if (self) {
// Initialization code here.
NSLog(#"Text Field initialised");
}
return self;
}
However this still doesn't seem to get called.
I assumed therefore that NSTextField just wasn't being subclassed. However, when I add this method to TextFieldClass :
-(void)textDidChange:(NSNotification *)notification {
NSLog(#"My text changed");
}
Run the app and lo and behold, every time I type in the text field I get the My text changed message in the Console.
So my question is, what is going on here? How does the NSTextField get initialized and how can you override it's initialiser?
Why does the Custom View seem to act differently to the NSTextField?
Source code here
For your first question, NSTextFiled gets initialised via
- (id)initWithCoder:(NSCoder *)aDecoder
In this case, you have dragged a NSTextField from the palette and then changed the class to your custom text field class in the identity inspector. Hence the initWithCoder: will be called instead of initWithFrame:. The same is true for any object (other than Custom View) dragged from the palette
Instead, if you drag "Custom View" from the palette and change the class to your custom text field class, the initWithFrame: will be invoked.
The CustomViewClass you have created is the second case, hence initWithFrame: is invoked. The TextFieldClass is the first case, hence initWithCoder: is invoked.
If you use the Interface Builder in XCode, you should use awakeFromNib to initialise your subclass.
- (void)awakeFromNib
{
// Your init code here.
}
If you want to use your subclass programatically and using the interface builder, then use code like this:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initView];
}
return self;
}
- (void)awakeFromNib
{
[self initView];
}
- (void)initView
{
// Your init code here
}

Displaying an UIImageView property from another class programmatically

Well, here's the situation:
I've got...
a custom class with an UIImageView-property, let's call it the Enemy-class
a ViewController
a NSMutableArray to help create multiple instances of Enemy (called enemies)
and what I want:
be able to create an unlimited amount of Enemy-Instances through a method in my ViewController (like [self spawnEnemy];, where self is the ViewController)
and, subsequently, display the UIImageView property (let's call it "enemyImage") on the view that is controlled by my ViewController
I've tried something like this:
-(Enemy *) spawnEnemy
{
Enemy *tempEnemy = [Enemy new];
[enemies addObject:(Enemy*)tempEnemy];
[self.view addSubview:(UIImageView*)[[enemies objectAtIndex:[enemies count]] enemyImage]];
//randomLocation is declared in the Enemy-Class and just assigns a random
//CGPoint to self.enemyImage.center
[[enemies objectAtIndex:[enemies count]] randomLocation];
return [[enemies objectAtIndex:[enemies count]]createEnemy];
}
This runs without errors, randomLocation gets called (tried with NSLog), AND if I do something like this in another Method of ViewController:
[[self spawnEnemy] enemyTestMethod];
enemyTestMethod is being executed as well.
But still, no enemieViews are displayed on the screen...
What am I doing wrong?
Thank you so much for your help and time.
==== Edit ====
Here's the relevant code from Enemy.h/Enemy.m:
#interface Enemy : NSObject
{
UIImageView *enemyImage;
}
#property (nonatomic, retain) IBOutlet UIImageView *enemyImage;
-(Enemy*) createEnemy;
//Enemy.m
#implementation Enemy
#synthesize enemyImage, speed;
-(Enemy *) createEnemy
{
self.enemyImage = [[UIImageView alloc] init];
[self.enemyImage setImage:[UIImage imageNamed:#"enemy.png"]];
return self;
}
I also corrected the last line in the spawnEnemy-Method to properly send createEnemy.
You don't include the code in Enemy where you alloc/init the UIImageView property. Unless this code explicitly specifies a CGRect with the size and origin that you want, the view will be initialized with CGRectZero, which means even if you're correctly adding the subview (and it looks like you are) you still won't see it anywhere.
Post the Enemy code, and the problem will probably be immediately apparent.
Have you called -createEnemy before you added them to your view?
OK, you've got this checked.
Then maybe you should check as #MusiGenesis suggested.
To do this, you need to inspect the properties of your enemyImage.
You can do this in either of the following ways:
print its frame.size by:
NSLog(#"enemyImage frame size: %#", NSStringFromCGRect(enemy.enemyImage));
set a breakpoint where you feel great, and check with your debugger:
p (CGRect)[[enemy enemyImage] frame]

Assignment of an ivar not working

I'm seeing a bizarre case where simple assignment to an instance variable is not working. The right hand object is non-nil, but refuses to be assigned to an instance variable (with the same type). This is not happening consistently within the app.
I have a class like this:
#interface FooViewController : UIViewController {
UIView *contentView;
BOOL testBool;
}
#end
#implementation FooViewController
- (void)viewDidLoad {
UIView *theContentView = [[UIView alloc] initWithFrame:self.view.bounds];
// this assignment fails for some reason:
contentView = [theContentView retain];
/* at this point, contentView == nil */
UIView *bar = theContentView; // this works
testBool = YES; // this works
}
#end
and a subclass like this:
#interface BarViewController : FooViewController {
}
#end
When I instantiate the BarViewController from a nib, when the execution gets to the viewDidLoad defined in the superclass, the strange behavior above happens. Instance variables "refuse to assign".
However, if I add a dummy instance variable to the subclass, the expected behavior returns. Instance variables correctly assign in the viewDidLoad.
I'm seeing this at runtime in iOS 4.3 in the simulator using Xcode 4.0.2. This happens both in LLVM+GCC and GCC.
This is not happening in isolation. When I try this in a test project, it performs normally.
I'm sorry, but I don't believe you.
My first question is how you determine that contentView is equal to nil? Do you have a breakpoint after the assignment? If you break on the line of the assignment, that line will not yet have been executed, so contentView will be nil at that point.
If you're testing contentView in some other method it might just be that viewDidLoad has not been run yet.
Show me the output of:
- (void)viewDidLoad {
UIView *theContentView = [[UIView alloc] initWithFrame:self.view.bounds];
// this assignment fails for some reason:
contentView = [theContentView retain];
/* at this point, contentView == nil */
NSLog(#"theContentView: %p", theContentView);
NSLog(#"contentView: %p", contentView);
UIView *bar = theContentView; // this works
testBool = YES; // this works
}
If they differ I'll start believing you. (Well, maybe not, I'll still consider the possibility that you're lying.)

firstResponder in NSViewController

I've got two classes. ManagingViewController, a subclass of NSViewController, and ViewController, a subclass auf ManagingViewController. In Viewcontroller I've got a NSTextField which I want to become the firstResponder, but I didn't manage that.
So it is nearly the same like the Chapter 29 in Hillegass' book Cocoa Programming for Mac OS X (Download of the book's examples) except of an NSTextField which is set to firstResponder.
Can anybody point me to the correct way?
You need to set the text field as the first responder by using -[NSWindow makeFirstResponder:].
Since this is an NSWindow method, it only makes sense after you’ve added the corresponding view to the window, i.e., after you’ve added the view as a subview inside the window view hierarchy. In the book’s example, this happens when you set the view as the content view of the box inside the window. For example:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
if ([vc class] == [ViewController class]) {
[w makeFirstResponder:[(ViewController *)vc myTextField]];
}
}
This assumes ViewController exposes a getter method called -myTextField.
You can make this more generic by having your view controllers expose a method that returns the object that the view controller recommends as the first responder. Something like:
#interface ManagingViewController : NSViewController
…
- (NSResponder *)recommendedFirstResponder;
#end
#implementation ManagingViewController
…
- (NSResponder *)recommendedFirstResponder { return nil; }
#end
And, in your concrete subclasses of ManagingViewController, have -recommendedFirstResponder return the object that should be the window’s first responder:
#implementation ViewController
…
- (NSResponder *)recommendedFirstResponder { return myTextField; }
#end
Having done that, you can change your -displayViewController: to something like:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
NSResponder *recommendedResponder = [vc recommendedFirstResponder];
if (recommendedResponder) [w makeFirstResponder:recommendedResponder];
}
Have you tried [[myTextField window] makeFirstResponder:myTextField]; ?
simple. Goto you xib file in interface builder. right click the first responder field. it will show the connection , remove the connection and connect it to the desired responder. let me know if this works

About drawing using quartz 2D on iPhone

I have a view, that have a drawRect method, I know that this method is the only way I control the View to draw something on it. So, I try to my drawing logic in this way:
- (void)drawRect:(CGRect)rect {
//my drawing code...
}
In my view, I use the IB to link this class.
[myView setNeedsDisplay];
It works, so, I designed to have a Command object inside the drawRect method, so that I can drawing dynamically based on my Cmd. Here is the code in the View after I modified:
- (void)drawRect:(CGRect)rect {
self.cmdToBeExecuted = [[DrawingSomethingCmd alloc] init];
[self.cmdToBeExecuted execute];
}
My DrawingsomthingCmd:
#implementation DrawingSomethingCmd
-(void)execute{
//my drawing code;
}
It works too. But the question is, how can I assign the self.cmdToBeExecuted dynamically.
Also, I changed my drawRect like this:
- (void)drawRect:(CGRect)rect {
[self.cmdToBeExecuted execute];
}
Because I have this to link with the IB,
IBOutlet myDrawingView *myView;
but after I type [myView ... ...], it don't allow me to get the variable cmdToBeExecuted. I ready make my variable accessible in .h:
#property (nonatomic, retain) Command *cmdToBeExecuted;
and .m also:
#synthesize cmdToBeExecuted;
Don't initialize the command inside the drawing rect. Initialize a default (maybe in viewDidLoad? it depends on what you are doing) somewhere as the view is created and then update them dynamically however you are doing this, whenever the need arises. So:
- (void)drawRect:(CGRect)rect
{
[[self commandToBeExecuted] execute];
}
and elsewhere:
// dynamically update the drawing
[myView setCommandToBeExected:[[[DrawingSomethingCommand alloc] init] autorelease]];