Why won't this draw a path? - objective-c

The context is that I am trying to plot a series of temperatures as a graph.
At the moment my application works as follows (as i understand it):
The application opens, openfile is used which initialises my document class this reads the modified csv file into an NSString "fileContents" which I then separate into an array of strings, each string containing the value without separaters or whitespace. As follows:
NSArray *temps = [fileContents componentsSeparatedByCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"\n|\r"]];
After that I initialise my custom view, the TemperatureGraph class, and immediately pass it the array which becomes the currentlog seen in the code below. Instance id is current.
Finally I invoke:
[current drawpath];
Which is the instance method below:
- (void) drawpath
{
NSBezierPath *program = [NSBezierPath bezierPath];
NSPoint st = NSMakePoint(0, 0);
[program moveToPoint:st];
[program setLineWidth:10];
[program setLineCapStyle:NSRoundLineCapStyle];
[[NSColor blueColor] setStroke];
[program stroke];
NSUInteger a, b;
a = [currentlog count];
b = 0;
while (b <= a) {
NSPoint new;
float x;
x = 5 * b;
new = NSMakePoint(x, [[currentlog objectAtIndex:b] floatValue]);
[program lineToPoint:new];
[program stroke];
b += 6;
}
}
Why won't it work? Definition of won't work: Application compiles and runs but I have a blank window.
Also please point out any rookie errors that are in there but don't affect code, as I am still in the learning stages here.

Drawing doesn't happen unless you have an active graphics context. I'm assuming the drawpath method above is inside an NSView subclass - well in there you need to override a method called drawRect: - it may even have been added in as a stub when you made the subclass?
Cocoa calls this method when it is time for the view to draw itself. This means that, when it is being called, a graphics context will be set up and and drawing code you do will actually appear on the screen.
Simply call your drawpath method from inside drawRect: and you should see your lines.
The general point to remember is that you don't call any drawing code yourself (except from within drawRect:- you either mark a view as needing display ([myView setNeedsDisplay:YES];, or let the system do that itself.

Related

How to switch two objects in objective-c

I am trying to find if collision occurs between two rectangles in objective-c. I thought one way to accomplish this would be detect the rectangle that is closest to 0,0 point then do rest of the work.
I wrote a function that takes two rectangle objects as parameters and does the math to calculate area, distance to origin etc....
So lets say rect1 is at (100,200) and rect1's width is 100 height 200, rect2 is at 150,150 and rect2's width is 100 height 200 this is calculated by function well enough.
If I switch rec1 and rect2 properties, so rect1 will be at 150,150 while rect2 will be at 100,200. And call following function
-(Rectangle*)intersect:(Rectangle*)rectA:(Rectangle*)rectB{
//check closest rectangle to 0,0 and switch rectangles
if (rectA.origin.x>rectB.origin.x) {
Rectangle *temporary = [[Rectangle alloc] init];
temporary=rectA;
rectA=rectB;
rectB=temporary;
[temporary release];
}
float rectAX = rectA.origin.x;
float rectAY = rectA.origin.y;
float rectBX = rectB.origin.x;
float rectBY = rectB.origin.y;
When I enable guard malloc and zombies I get following error:
-[Rectangle origin]: message sent to deallocated instance 0x100acffd0
As soon as rectA.origin.x; is called I get the error.
So Howcome rectA or rectB is deallocated? What is the correct way to switch two objects that has bunch of properties ?
There is a built in function for comparing CGRects CGRectIntersectsRect(rectA, rectB) that you can use to check your rectangle's frames :)
As far as your code for switching you have created a third object by allocing temporary. Then you set the temporary pointer at rectA and then you release rectA at the end since its pointing to temporary. Leaving the newly created object as a leak and then sending messages to the released rectA.
You don't really want to swap object pointers like that if you can help it in my experience. But if you absolutely have to and understand what's going on you could do it like this:
// Create copies of your objects
Rectangle *rectACopy = [rectA copy];
Rectangle *rectBCopy = [rectB copy];
// release the originals.
[rectA release];
[rectB release];
// Set your copies to the original pointers.
rectA = rectBCopy;
rectB = rectACopy;
NSCopying Protocol
First you need to implement the protocol.
#interface Rectangle : NSObject <NSCopying>
Then you need to create the new method. This will create a new object but with all the same values.
- (id)copyWithZone:(NSZone *)zone
{
id copy = [[[self class] alloc] init];
if (copy) {
// Copy NSObject based properties like UIViews, NSArrays, Subclassed objects.
[copy setObjectProperty:[self.objectProperty copy]];
// Set primitives like ints, CGRects, Bools.
[copy setPrimitiveProperty:self.primitiveProperty];
}
return copy;
}
You don't need to allocate a new object instance for temporary (and therefore you don't need to release it either). You are just taking your 2 existing pointers and switching them around. You're correct to use a 3rd variable (temporary) but you don't need to allocate any new space because you're not moving anything in memory, just swapping which variables point to the existing objects.
-(Rectangle*)intersect:(Rectangle*)rectA:(Rectangle*)rectB{
//check closest rectangle to 0,0 and switch rectangles
if (rectA.origin.x>rectB.origin.x) {
//Rectangle *temporary = [[Rectangle alloc] init]; // here you don't need to allocate as you are not using this object
// So use
Rectangle *temporary=rectA;
rectA=rectB;
rectB=temporary;
//[temporary release]; //you don't need this. Here you were releasing rectA not the temp rect that you allocated just below if, as you assign rectA to temporary
}
float rectAX = rectA.origin.x;
float rectAY = rectA.origin.y;
float rectBX = rectB.origin.x;
float rectBY = rectB.origin.y;

cocos2d sub-classing

I know that cocos2d has scheduling callbacks to do nice things but when you need to use one CCAction (like CCMoveTo one) in order to move a sprite from position a to b, you do not have the ability to make small position arrangements to the sprite position for as long as the action is in effect.
The only possible way I found is by making a sub-class of CCMoveTo in order to check for obstacles and therefore provide some kind of movement to the left or right to a sprite that was moving from top to the bottom of the iPhone screen. The problem is that the sub-class does not have access to the parent class' instance variables (like the startPosition_ one) because they have not been declared as properties.
So I used the following snippet to overcome this situation but I wonder if I am doing something wrong...
- (void)myUpdate:(ccTime)time {
if(delegate && method_) {
NSNumber *num = (NSNumber *)[delegate performSelector:method_ withObject:ownTarget];
if(num) {
double xpos = [num doubleValue];
[num release];
CCMoveTo *parent = [super retain];
parent->startPosition_.x += xpos;
[parent release];
}
[super update:time];
}
Is it correct to retain/release the super-class? The "[super update:time];" at the bottom of the code will make the final positioning.
CCMoveTo *parent = [super retain];
Ouch! This statement makes absolutely no sense. It is the same as writing:
[self retain];
As for accessing the super class' instance variables: unless they're declared #private you can access them. I just checked: they're not #private. You should be able to write in your subclass:
startPosition_.x += xpos;
If that doesn't work make sure your class is really a subclass of CCMoveTo, and not some other class.
Finally, I'd like to say that actions are very limited when it comes to implementing gameplay. You're probably much better off to simply animate your game objects by modifying their position property every frame, based on a velocity vector. You have much more freedom over the position and position updates, and none of the side effects of actions such as a one-frame delay every time you run a new action.
-(void) update:(ccTime)delta
{
// modify velocity based on whatever you need, ie gravity, or just heading in one direction
// then update the node's position by adding the current velocity to move it:
self.position = CGPointMake(self.position.x + velocity.x, self.position.y + velocity.y);
}

Collision detection using mulitple subviews within a moving view

I am working on a game in OBJ C that has a ball view and a stage view. The stage view has 4 subviews. All views are UIImageViews. I have a method for collision detection that is working. I would like to expand it to more than 4 subviews without simply creating more lines of code. Looking at the code below, is there a way to simplify this into loops instead. Thanks!
// convert each square to be relevant to ball's superview in order to collision detect
CGRect square_01Frame = [ball.superview convertRect:square_01.frame fromView:square_01.superview];
CGRect square_02Frame = [ball.superview convertRect:square_02.frame fromView:square_02.superview];
CGRect square_03Frame = [ball.superview convertRect:square_03.frame fromView:square_03.superview];
CGRect square_04Frame = [ball.superview convertRect:square_04.frame fromView:square_04.superview];
// convert CGRects to NSStrings for storage in square_frames array
NSString *square_01FrameString = NSStringFromCGRect(square_01Frame);
NSString *square_02FrameString = NSStringFromCGRect(square_02Frame);
NSString *square_03FrameString = NSStringFromCGRect(square_03Frame);
NSString *square_04FrameString = NSStringFromCGRect(square_04Frame);
// load array of NSStrings
[square_frames replaceObjectAtIndex:0 withObject:square_01FrameString];
[square_frames replaceObjectAtIndex:1 withObject:square_02FrameString];
[square_frames replaceObjectAtIndex:2 withObject:square_03FrameString];
[square_frames replaceObjectAtIndex:3 withObject:square_04FrameString];
// create a for loop
for (int i=0; i<4; i++) { // 4 squares
// create test frame
CGRect test_frame = CGRectFromString([square_frames objectAtIndex:i]);
if (CGRectIntersectsRect(test_frame,ball.frame)) { // collision detection
// do something
}
}
Well, I would do a number of things.
First, I would create a ball "model", just an NSObject subclass to represent the Ball. Probably, that would have a property "location" or something, which is the CGRect.
Then, your current view could have an array of ball objects on the screen, and just loop through them.
Overall, though, I don't think using UIView's rects is the best way to manage collision detection. I think you'd be better off defining that in some other way, and then simply updating the UI accordingly.
Generally, it's not a good idea to rely on your UI implementation for game design. It makes it hard to change (as you note in your question).

Why I can't draw in a loop? (Using UIView in iPhone)

I can draw many things using this :
NSString *imagePath = [[NSBundle mainBundle] pathForResource:#"dummy2.png" ofType:nil];
UIImage *img = [UIImage imageWithContentsOfFile:imagePath];
image = CGImageRetain(img.CGImage);
CGRect imageRect;
double x = 0;
double y = 0;
for (int k=0; k<someValue; k++) {
x += k;
y += k;
imageRect.origin = CGPointMake(x, y);
imageRect.size = CGSizeMake(25, 25);
CGContextDrawImage(UIGraphicsGetCurrentContext(), imageRect, image);
}
}
CGImageRelease(img.CGImage);
So, it works, so, I put it into a command object's execute method. Then, I want to do similar thing, but this time, my execute method only do this:
NSString *imagePath = [[NSBundle mainBundle] pathForResource:#"dummy2.png" ofType:nil];
UIImage *img = [UIImage imageWithContentsOfFile:imagePath];
image = CGImageRetain(img.CGImage);
CGRect imageRect;
double x = inComingX;
double y = inComingY;
imageRect.origin = CGPointMake(x, y);
imageRect.size = CGSizeMake(25, 25);
CGContextDrawImage(UIGraphicsGetCurrentContext(), imageRect, image);
CGImageRelease(img.CGImage);
This time, this is also a Command, and it is the execute method. But I take the for loop away. I will have another method that pass the inComingX , and inComingY into my Command object.
My Drawing method is simply execute the Cmd that passed in my drawingEngine:
-(void)drawInContext:(CGContextRef)context
{
[self.cmdToBeExecuted execute];
}
I also have the assign method to assign the command,:
-(void)assignCmd:(Command* )cmd{
self.cmdToBeExecuted = cmd;
}
And this is the way I called the drawingEngine
for(int k=0; k<5; k++){
[self.drawingEngine assignCmd:[DrawingCmd setDrawingInformation:(10*k):0:#"dummy.png"]];
[self.drawingEngine setNeedsDisplay];
}
It can draw, but the sad thing is it only draw the last one. Why? and how to fix it? I can draw all the things in my First code, but after I take the loop outside, and use the loop in last code, it just only draw the last one. Plz help
That's because setNeedsDisplay does not actually call drawRect:. It simply schedules the view to be redrawn at the next "convenient" time, which is likely the next time, the application re-enters the run-loop. Since you overwrite the remembered command object on each call to the assignment function, by the time, the drawRect: is actually called, only the last assigned command is available and will be drawn.
A better way to do it would be: remember all commands to be drawn instead just the last one, say in an array, like:
#interface MyCanvas {
...
NSMutableArray* commandList;
...
}
and add commands to that array instead of assigning a single command member:
-(void) addCommand:(Command*) cmd {
[self.commandList addObject: cmd];
}
The commands should then be processed in your draw method
for( Command* cmd in self.commandList ) {
[cmd execute ...];
}
Alternatively, you could define "complex" commands, which consist of more than a single drawing step.
(EDIT to answer the question in the comments): Your original code did work, because it does the work all in one place in a single invocation of the appropriate draw method. Your last code does not draw anything at all while it runs. It simply remembers (via command object) that something has to be done, and notifies the view, that it should redraw itself on the next convenient occasion. It is important to note, that setNeedsDisplay will not cause any repainting to be done directly. It simply marks the view as "dirty", which will be picked up by other code in the Cocoa run-time later.
There is another thing in your code which I find slightly suspicious: your method drawInContext: takes a context argument, which is simply ignored. Neither is it passed on to the execute method of you command object, nor is it installed as some kind of "current" drawing context in an instance variable or somesuch. If you expect the code in drawRect: (or the command's execute method) to actually use that context, you have to pass it on to whoever is supposed to use it.

Is there somethone wrong with my Object Scope?

This is a program I'm writing (myself as opposed to copying someone else's and thus not learning) as part of the ObjectiveC and Cocoa learning curve. I want to draw simple shapes on a NSView (limiting it to ovals and rectangles for now). The idea is that I record each NSBezierPath to an NSMutableArray so I can also investigate/implement saving/loading, undo/redo. I have a canvas, can draw on it as well as 2 buttons that I use to select the tool. To handle the path I created another object that can hold a NSBezierPath, color values and size value for each object drawn. This is what I want to store in the array. I use mouseDown/Dragged/Up to get coordinates for the drawing path. However, this is where things go wonky. I can instantiate the object that is supposed to hold the path/color/etc. info but, when I try to change an instance variable, the app crashes with no useful message in the debugger. I'll try to keep my code snippets short but tell me if I need to include more. The code has also degenerated a little from me trying so many things to make it work.
Project: Cocoa document based app
I have the following .m/.h files
MyDocument:NSDocument - generated by XCode
DrawnObject:NSObject - deals with the drawn object i.e. path, color, type (oval/rect) and size
Canvas:NSView - well, shows the drawing, deals with the mouse and buttons
Canvas is also responsible for maintaining a NSMutableArray of DrawnObject objects.
DrawnObject.h looks like this:
#import <Foundation/Foundation.h>
//The drawn object must know what tool it was created with etc as this needs to be used for generating the drawing
#interface DrawnObject : NSObject {
NSBezierPath * aPath;
NSNumber * toolType;//0 for oval, 1 for rectangular etc....
float toolSize;
struct myCol{
float rd;
float grn;
float blu;
float alp;
} toolColor;
}
-(void)setAPath:(NSBezierPath *) path;
-(NSBezierPath *)aPath;
#property (readwrite,assign) NSNumber * toolType;
-(float)toolSize;
-(void)setToolSize:(float) size;
-(struct myCol *)toolColor;
-(void)setCurrentColor:(float)ref:(float)green:(float)blue:(float)alpha;
#end
Canvas.h looks like this
#import
#import "drawnObject.h"
#interface Canvas : NSView {
NSMutableArray * myDrawing;
NSPoint downPoint;
NSPoint currentPoint;
NSBezierPath * viewPath;//to show the path as the user drags the mouse
NSNumber * currentToolType;
BOOL mouseUpFlag;//trying a diff way to make it work
BOOL mouseDrag;
}
-(IBAction)useOval:(id)sender;
-(IBAction)useRect:(id)sender;
-(IBAction)showTool:(id)sender;
-(NSRect)currentRect;
-(NSBezierPath *)createPath:(NSRect) aRect;
-(void)setCurrentToolType:(NSNumber *) t;
-(NSNumber *)currentToolType;
#end
In the Canvas.m file there are several functions to deal with the mouse and NSView/XCode also dropped in -(id)initWithFrame:(NSRect)frame and -(void)drawRect:(NSRect)rect Originally I use mouseUp to try to insert the new DrawnObject into the array but that caused a crash. So, now I use two BOOL flags to see when the mouse was released (clunky but I'm trying....)in drawRect to insert into the array. I've included the method below and indicated where it causes the app to fail:
- (void)drawRect:(NSRect)rect { //This is called automatically
// Drawing code here.
//NSLog(#"Within drawRect tool type is %d", [self currentTool]);
NSRect bounds = [self bounds];
NSRect aRect = [self currentRect];
viewPath = [self createPath:aRect];
//the createPath method uses the tool type to switch between oval and rect bezier curves
if(mouseUpFlag==YES && mouseDrag==YES){
mouseDrag=NO;
//Create a new drawnObject here
DrawnObject * anObject = [[DrawnObject alloc]init];//- WORKS FINE UP TO HERE
NSLog(#"CREATED NEW drawnObject");
[anObject setAPath:viewPath]; //- INSTANT APP DEATH!!!!
NSLog(#"Set a path in drawnObject");
[anObject setToolType:[[NSNumber alloc]initWithInt:5]];
NSLog(#"Set toolType in DrawnObject");
[anObject setToolType:currentToolType];
[myDrawing addObject:anObject];
NSLog(#"Added Object");
}
[[NSColor colorWithCalibratedRed:0.0 green:0.9 blue:0.0 alpha:0.5]set];
[NSBezierPath fillRect:bounds];
[[NSColor lightGrayColor]set];
[viewPath stroke]; //This is so the user can see where the drawing is being done
//Now, draw the paths in the array
[[NSColor blueColor]set];
for(DrawnObject * indexedObject in myDrawing){
[[indexedObject aPath] stroke];//This will do the actual drawing of ALL objects
}
}
I guess this has something to do with object scope or something but I just can not figure it out. As I said, as I've tried things the code has sort of undergone an metamorphosis, sadly not for the better. Like those BOOLS etc.
HELP! Any clever people out there, point me in the right direction please!
ADDED THIS ON:
-(NSBezierPath *)createPath:(NSRect) aRect
{
NSBezierPath * tempPath;
//I need to know what tool
switch(0){ //temporary - this would use the toolType as a selector
case 0:
tempPath = [NSBezierPath bezierPathWithOvalInRect:aRect];
break;
case 1:
tempPath = [NSBezierPath bezierPathWithRect:aRect];
break;
default:
tempPath = [NSBezierPath bezierPathWithOvalInRect:aRect];
break;
}
return tempPath;
}
You said your init method was:
-(void)init {
[super init];
//set default color = black
toolColor.rd=1.0;
toolColor.grn=1.0;
toolColor.blu=1.0;
toolColor.alp=1.0;
//set default size
toolSize=0.8;
//set default toolType
toolType=0;
//oval
NSLog(#"Init %#",self);
}
This is definitely wrong; read up on how to create an init method in the Obj-C guide or by reading sample code. Here's what it should look like:
-(id)init {
if (self = [super init]) {
//set default color = black
toolColor.rd=1.0;
toolColor.grn=1.0;
toolColor.blu=1.0;
toolColor.alp=1.0;
//set default size
toolSize=0.8;
//set default toolType
toolType=0;
//oval
NSLog(#"Init %#",self);
}
return self;
}
By not returning anything from -init, you were preventing the object's creation. Good luck! :-)
Edit: Ashley beat me to it...
What do you mean by “crash”?
Does anything appear in the Debugger Console (⇧⌘R)?
Does a stack trace appear in the Debugger window?
If there's a stack trace, where in your code does it crash?
It just hangs. In the debugger I see:
[Session started at 2008-11-28 14:40:34 +1000.]
2008-11-28 14:40:36.157 CH18Challenge_try2[1893:10b] Mouse Down at (80.000000,285.000000)
2008-11-28 14:40:36.333 CH18Challenge_try2[1893:10b] Mouse Up at (166.000000,217.000000)
2008-11-28 14:40:36.348 CH18Challenge_try2[1893:10b] Init
2008-11-28 14:40:36.349 CH18Challenge_try2[1893:10b] CREATED NEW drawnObject
[Session started at 2008-11-28 14:40:36 +1000.]
Loading program into debugger…
GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin".Program loaded.
sharedlibrary apply-load-rules all
Attaching to program: `/Users/johan_kritzinger/Documents/Cocoa/CH18Challenge_try2/build/Debug/CH18Challenge_try2.app/Contents/MacOS/CH18Challenge_try2', process 1893.
(gdb)
Then I have to force quit to stop it.
We need to see the implementation of setAPath from DrawnObject.m. Also, for the "stack trace" look on the upper left of the debugger--it should list a stack of functions showing where in your code the crash is. Make sure you're running in Debug mode, not Release.
On the command line you can type print-object and you can
set a breakpoint in that line and step through it from there. It seems setAPath is somehow broken
Regards
Friedrich
What you have is not a crash. A crash is when a signal is raised (like EXC_BAD_ACCESS) or an uncaught exception.
What you have seems to be an infinite loop.
You need to use the pause button in the Debugger and see exactly where. I would guess that you have an infinite loop in your setAPath: method. You need to work out why this function is looping indefinitely.