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

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.

Related

SpriteKit - Run a method with two arguments after a random period of time

While trying to create a game for iOS I'm facing a problem: I cannot find a way to call a method that creates a SpriteKit node from another class that "calls itself" or repeats after a random period of time in an easy way.
The idea is this: I have a class where the scene is created. But then I have another class (subclass of SKSPriteNode) that creates the different SKSpriteNode that I need. I have one method called createObjectWithName: name position: position that takes two arguments (name and position). I need to call this method from my scene (fine until here), but I also need to repeat this method constantly in random periods of time. So, once it is called one time, it calls itself after a period of time, creating more SKSPriteNodes.
I've tried using performSelector and dispatch_after, but I hadn't had any luck so far.
Thank you in advance.
Unless I am missing something, I think you want to use SKAction for this solution.
You could have a method for starting the spawner like this :
-(void)startSpawner:(float)duration range:(float)range
{
SKAction *delay = [SKAction waitForduration:duration withRange:range];
SKAction *spawnBlock = [SKAction runBlock:^(void)
{
NSString *spawnName = #"name";
CGPoint *spawnPosition = CGPointMake(someX, someY);
SpriteNodeSubclass *node = [SpriteNodeSubclass createObjectWithName:spawnName andPosition:spawnPosition];
// do something with that node if you need to.
}];
SKAction *sequence = [SKAction sequence:#[delay, spawnBlock]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];
}
I think it's ideal to use SKAction as opposed to dispatch_after, because if you pause SpriteKit, the SKAction will also pause.
You can store a timestamp property in your scene class (let's call it timeElapsedFromLastSpawn and initialize it with 0). Then you can use this property in your update method :
timeElapsedFromLast += timeElapsedFromLastUpdate;
if (timeElapsedFromLast > 5.0) {
[self spawnSpriteNode];
timeElapsedFromLast = 0;
}
This will spawn a new sprite every 5 seconds. (And you can randomize it easily)
I would also recommend for the spawning method to be not in a SKSpriteNode instance but outside (e.g. in the parenting scene/node class) as SKSpriteNode role is to represent a sprite and not being a factory of sprites (unless it creates child sprites for it to control directly)
EDIT:
To calculate timeElapsedFromLastUpdate you can use the following code (taken from Ray Wenderlich's site which has great tutorials about this stuff)
- (void)update:(NSTimeInterval)currentTime {
// Handle time delta.
// If we drop below 60fps, we still want everything to move the same distance.
CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
self.lastUpdateTimeInterval = currentTime;
if (timeSinceLast > 1) { // more than a second since last update
timeSinceLast = 1.0 / 60.0;
self.lastUpdateTimeInterval = currentTime;
}
[self updateWithTimeSinceLastUpdate:timeSinceLast];
}
you should implement updateWithTimeSinceLastUpdate method in your scene class or use the calculation directly in the update method above
If this is how you call the method from your scene:
[otherClass createObjectWithName:name position:position];
Then this is how you can use GCD to schedule that call for a random amount of time later:
dispatch_after(dispatch_time(
DISPATCH_TIME_NOW,
(int64_t)(arc4random_uniform((u_int32_t)(MAX_SECONDS * NSEC_PER_SEC)))),
dispatch_get_main_queue(),
^{
[otherClass createObjectWithName:name position:position];
});
(assuming MAX_SECONDS * NSEC_PER_SEC is small enough to be represented by a 32-bit quantity; otherwise look at piling two arc4random_uniform calls with suitable modulo arithmetic)
So a way to that in a repeating fashion, with a __weak safeguard to prevent arbitrary extension of the lifetime of your object could be to use a tail call:
- (void)scheduleNextObject
{
__weak YourClass *weakSelf = self;
dispatch_after(dispatch_time(
DISPATCH_TIME_NOW,
(int64_t)(arc4random_uniform((u_int32_t)(MAX_SECONDS * NSEC_PER_SEC)))),
dispatch_get_main_queue(),
^{
// weakSelf prevents self itself from being captured, so that self
// can be deallocated even with this loop ongoing. We'll explicitly
// check whether what was self still exists to stick with the idiom,
// though it's strictly unnecessary
YourClass *strongSelf = weakSelf;
if(!strongSelf) return;
[otherClass createObjectWithName:name position:position];
[strongSelf scheduleNextObject];
});
}

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;

How do i set a CGContextRef property rather than using UIGraphicsGetCurrentContext()

Ive been trying to work with UIGraphicsGetCurrentContext and CGContextRef. I was told that using UIGraphicsGetCurrentContext() numerous times is bad and to rather work on using CGContextRef and referring to it.
I have been trying to work on the second part and I am having issues setting the #property and referring to it. Can someone give me a declaration and usage example of this? Tried googling and couldn't find any references to it.
Ta
You probably shouldn't store the return value from UIGraphicsGetCurrentContext in a property. You usually either don't know how long the context will be valid, or the context has a short lifetime anyway. For example, if you're calling UIGraphicsGetCurrentContext from your drawRect: method, you don't know how long that context will survive after you return from drawRect:. If you're calling UIGraphicsGetCurrentContext after calling UIGraphicsBeginImageContextWithOptions, you will probably be calling UIGraphicsEndImageContext soon anyway. It would be inappropriate to keep references to those contexts around.
If you are calling many Core Graphics functions on the same context, then you want to store the context in a local variable. For example, here is a drawRect: method from one of my test projects:
- (void)drawRect:(CGRect)dirtyRect {
NSLog(#"drawRect:%#", NSStringFromCGRect(dirtyRect));
[self layoutColumnsIfNeeded];
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSaveGState(gc); {
// Flip the Y-axis of the context because that's what CoreText assumes.
CGContextTranslateCTM(gc, 0, self.bounds.size.height);
CGContextScaleCTM(gc, 1, -1);
for (NSUInteger i = 0, l = CFArrayGetCount(_columnFrames); i < l; ++i) {
CTFrameRef frame = CFArrayGetValueAtIndex(_columnFrames, i);
CGPathRef path = CTFrameGetPath(frame);
CGRect frameRect = CGPathGetBoundingBox(path);
if (!CGRectIntersectsRect(frameRect, dirtyRect))
continue;
CTFrameDraw(frame, gc);
}
} CGContextRestoreGState(gc);
}
You can see that I'm doing a bunch of stuff with the context: I'm saving and restoring the graphics state, I'm changing the CTM, and I'm drawing some Core Text frames into it. Instead of calling UIGraphicsGetCurrentContext many times, I call it just once and save the result in a local variable named gc.

Why won't this draw a path?

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.

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).