object notifications other than touches* don't work - c4

I am trying to attach notifications other than touchesBegan to a C4Shape and I am getting nothing. I have added a C4Shape and have tried to add other notifications at the bottom of setup. The problem is that the only notification that ever gets called is touchesBegan.
I also tried adding gestures a la http://www.c4ios.com/examples/listenFor.php but when I do that touchesBegan notifications stop from that object.
#import "C4WorkSpace.h"
#implementation C4WorkSpace {
C4Shape * topthing;
}
-(void)setup {
C4Font * f = [C4Font fontWithName:#"ArialRoundedMTBold" size:50.0f];
topthing = [C4Shape shapeFromString:#"touch me" withFont:f];
[topthing setCenter:CGPointMake(self.canvas.center.x, self.canvas.height/6)];
[self.canvas addSubview:topthing];
[topthing setAnimationDuration:2.0];
[self listenFor:#"touchesBegan" fromObject:topthing andRunMethod:#"bangg:"];
[self listenFor:#"tapped" fromObject:topthing andRunMethod:#"bangg:"];
[self listenFor:#"longPress" fromObject:topthing andRunMethod:#"bangg:"];
[self listenFor:#"swipedRight" fromObject:topthing andRunMethod:#"bangg:"];
[self runMethod:#"fffff" afterDelay:1.0];
}
-(void) fffff {
[topthing rect:CGRectMake(0, 0, self.canvas.width, self.canvas.height/3)];
[topthing setAnimationDuration:0.0];
}
- (void) bangg:(NSNotification *) notification {
C4Log(#"%#", [notification name]);
}
#end

Two things happening here...
1) Listening for gesture broadcasts isn't enough. You need to actually add the gestures to the shape itself so that it will broadcast. For the 3 gestures you're listening for you should use the following code:
[topthing addGesture:TAP name:#"tap" action:#"tapped:"];
[topthing addGesture:LONGPRESS name:#"long" action:#"pressedLong"];
[topthing addGesture:SWIPERIGHT name:#"right" action:#"swipedRight:"];
[self listenFor:#"tapped" fromObject:topthing andRunMethod:#"bang1:"];
[self listenFor:#"pressedLong" fromObject:topthing andRunMethod:#"bang2:"];
[self listenFor:#"swipedRight" fromObject:topthing andRunMethod:#"bang3:"];
2) When you add gestures to a shape the expected behaviour is to turn off touchesBegan. The reasoning for this is in the case where you add a LONGPRESS gesture to an object you would expect the method for your LONGPRESS gesture to run after a certain amount of time. If you don't turn off touchesBegan then BOTH methods run: first touchesBegan gets triggered, then your longPress method gets triggered which can be troublesome if you want ONLY the long press method to run. This same logic applies to all gestures.
If you want to allow touchesBegan to run you can do the following:
[topthing gestureForName:#"tap"].delaysTouchesBegan = NO;
[topthing gestureForName:#"long"].delaysTouchesBegan = NO;
[topthing gestureForName:#"right"].delaysTouchesBegan = NO;
...note that it has to be done for all gestures active on the object.
If you plan to add complex gesture functionality to your objects, I would suggest subclassing and overriding the individual methods like -(void)tapped{} and -(void)swipedRight{}. This will allow you to more easily customize actions rather than dealing with them from the canvas.
#import "C4WorkSpace.h"
#implementation C4WorkSpace { C4Shape * topthing; }
-(void)setup {
C4Font * f = [C4Font fontWithName:#"ArialRoundedMTBold" size:50.0f];
topthing = [C4Shape shapeFromString:#"touch me" withFont:f];
[topthing setCenter:CGPointMake(self.canvas.center.x, self.canvas.height/6)];
[self.canvas addSubview:topthing];
[topthing setAnimationDuration:2.0];
[topthing addGesture:TAP name:#"tap" action:#"tapped:"];
[topthing addGesture:LONGPRESS name:#"long" action:#"pressedLong"];
[topthing addGesture:SWIPERIGHT name:#"right" action:#"swipedRight:"];
[self listenFor:#"tapped:" fromObject:topthing andRunMethod:#"bang1:"];
[self listenFor:#"pressedLong" fromObject:topthing andRunMethod:#"bang2:"];
[self listenFor:#"swipedRight" fromObject:topthing andRunMethod:#"bang3:"];
[self runMethod:#"fffff" afterDelay:1.0];
}
-(void) fffff {
[topthing rect:CGRectMake(0, 0, self.canvas.width, self.canvas.height/3)];
[topthing setAnimationDuration:0.0];
}
-(void)bang1:(NSNotification *)notification {C4Log(#"%#",[notification name]);}
-(void)bang2:(NSNotification *)notification {C4Log(#"%#",[notification name]);}
-(void)bang3:(NSNotification *)notification {C4Log(#"%#",[notification name]);}
#end

Related

How to work without a draw( ); loop in C4

So I'm coming from a bit of a processing background (albiet not much) where I'm used to working with a draw loop that runs over and over when the app is running.
I can't seem to find something similar in xcode and C4. Is there anything like that anyone can suggest?
What I'm basically doing is just creating a bouncing ball app using C4 vectors as properties in a custom class
Here is my custom class Header/Implementation file:
#interface splitSquare : C4Shape
#property C4Vector *accel;
#property C4Vector *velocity;
#property C4Vector *location;
-(id)initWithPoint:(CGPoint)point;
#end
-(id)initWithPoint:(CGPoint)point{
self = [super init];
if (self){
CGRect frame = CGRectMake(0, 0, 50, 50);
[self ellipse:frame];
self.lineWidth = 0.0f;
self.fillColor = [UIColor colorWithRed:[C4Math randomInt:100]/100.0f
green:[C4Math randomInt:100]/100.0f
blue:0.5f
alpha:1.0f];
_location = [C4Vector vectorWithX:point.x Y:point.y Z:0.0f];
self.origin = _location.CGPoint;
_accel = [C4Vector vectorWithX:0.04f Y:0.05f Z:0.0f];
_velocity = [C4Vector vectorWithX:0.004f Y:0.0005f Z:0.0f];
C4Log(#"The current value of velocity x is %f and y is %f", _velocity.x, _velocity.y);
}
I'm doing something pretty simple (adding accel to velocity and velocity to location, then assign location to the shapes origin). In the main C4WorkSpace I have this code:
#interface C4WorkSpace()
-(void)updateVectors;
#end
#implementation C4WorkSpace
{
splitSquare *testShape;
C4Timer *timer;
}
-(void)setup {
testShape = [[splitSquare alloc] initWithPoint:point2];
[self.canvas addShape:testShape];
testShape.animationDuration = 0.25f;
timer = [C4Timer automaticTimerWithInterval:0.25f target:self method:#"updateVectors" repeats:YES];
}
-(void)updateVectors{
//accel add to vel, vel added to location
[testShape.velocity add:testShape.accel];
[testShape.location add:testShape.velocity];
testShape.origin = testShape.location.CGPoint;
testShape.fillColor = [UIColor colorWithRed:[C4Math randomInt:100]/100.0f
green:[C4Math randomInt:100]/100.0f
blue:0.5f
alpha:1.0f];
}
#end
So this is what I'm doing now with a C4 timer getting called, but I feel like there has to be a more elegant way to do this. Right now the animation is jumpy and although it doesn't throw an error it doesn't seem to work properly (every time I run the iOS simulator it runs for a second then jumps back into xcode and shows me the current thread).
Any/all suggestions would be awesome thanks!
You're actually right on track with using a timer to create an update loop. Instead of having an entire update for the screen, C4 updates only the content it needs to update at any given time. This is why there's no draw loop like in other apis. A major reason for this is to be lighter on system resources (i.e. less drawing / less updating = longer battery life for mobile devices).
To address your issues above, I've slightly modified the code you sent over.
Here's the splitSquare header:
#import "C4Shape.h"
#interface splitSquare : C4Shape
#property C4Vector *accel;
#property C4Vector *velocity;
#property C4Vector *location;
-(id)initWithPoint:(CGPoint)point;
-(void)beginUpdating;
#end
NOTE: I added a beginUpdating method to the header.
Here's the splitSquare implementation:
#import "splitSquare.h"
#implementation splitSquare {
C4Timer *updateVectorTimer;
}
-(id)initWithPoint:(CGPoint)point{
self = [super init];
if (self){
CGRect frame = CGRectMake(0, 0, 50, 50);
[self ellipse:frame];
self.lineWidth = 0.0f;
self.fillColor = [UIColor colorWithRed:[C4Math randomInt:100]/100.0f
green:[C4Math randomInt:100]/100.0f
blue:0.5f
alpha:1.0f];
_location = [C4Vector vectorWithX:point.x Y:point.y Z:0.0f];
self.origin = _location.CGPoint;
_accel = [C4Vector vectorWithX:0.04f Y:0.05f Z:0.0f];
_velocity = [C4Vector vectorWithX:0.004f Y:0.0005f Z:0.0f];
C4Log(#"The current value of velocity x is %f and y is %f",
_velocity.x,
_velocity.y);
}
return self;
}
-(void)beginUpdating {
updateVectorTimer = [C4Timer automaticTimerWithInterval:1/60.0f
target:self
method:#"updateVectors"
repeats:YES];
}
-(void)updateVectors{
//accel add to vel, vel added to location
[self.velocity add:self.accel];
[self.location add:self.velocity];
self.origin = self.location.CGPoint;
self.fillColor = [UIColor colorWithRed:[C4Math randomInt:100]/100.0f
green:[C4Math randomInt:100]/100.0f
blue:0.5f
alpha:1.0f];
}
#end
NOTE: I added the timer directly to the splitSquare object.
The main thing for making your animation smoother happens in the beginUpdating method when you create the update timer.
Originally, you were animating for 0.25 seconds before updating and triggering the timer every 0.25 seconds as well. I've changed the animation duration so that it is immediate (0.0f seconds).
I then changed the update timer to run at 60fps by triggering the timer at '1/60.0f' seconds so that the animation is smoother.
I made an alternative implementation (just to show that it can be done this way as well) by adding the animation and timer to the splitSquare method itself. The C4WorkSpace changed to the following:
#import "C4WorkSpace.h"
#import "splitSquare.h"
#implementation C4WorkSpace {
splitSquare *testShape;
}
-(void)setup {
testShape = [[splitSquare alloc] initWithPoint:CGPointMake(100,100)];
[self.canvas addShape:testShape];
[testShape beginUpdating];
}
#end
Instead of managing the animation of the object itself, the workspace simply starts tells the object to beginUpdating.
A bit about what's going on.
One of the main differences, as you know, is that there is no default draw loop. Part of the reason for this is that as the developer you're almost never really dealing with the actual drawing/rendering of objects to the screen. When you create a visual object and add that to the canvas, the rendering is done by the system itself so that drawing is much more efficient on system resources. This is the ideal way of dealing with media not only in C4 but also in iOS programming.
Not having a draw loop is definitely different and takes some getting used to. But, it's really quite a nice way of working.
In essence, the draw-loop in other apis gets triggered by something like a timer every 30 or 60 seconds. So, adding a timer the way you did to update your shape is exactly what you want to do if you need loop-like functionality.
The nice thing about NOT having a timer in C4 is that you can simply add one to your workspace to control the firing of updates. BUT an even nicer thing is that you can add timers to individual objects so that they can update themselves. This means that you can also have multiple timers in multiple objects updating at different times / delays to create a more dynamic set of actions than you could ever get with only a single running loop.

AVAudioPlayer stops

I'm trying to play a mp3 file with using AVAudioPlayer:
NSString *path = NSString stringWithFormat:#"%#/%#" ,
[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSURL* url = [NSURL fileURLWithPath:path];
AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: nil];
[player play];
NSLog(#"%f/%f", player.currentTime, player.duration);
However, the player stops around 478.558367 every time even though its duration is 1044.437506.
Why does the player stop in the middle of the track?

Cocos3d: Crashing when loading scene in separate thread or background thread:

I am trying to load several big models in code and show the scenes. Generally, it is taking long time for loading and showing scene on screen as it need to extract lot of resources from a pod model.
So, i thought of populate the first scene in main thread and remaining others in a separate thread. But, it is crashing when i move my part code into separate thread. Here is my sample code:
-(void) loadFirstScene
{
CC3PODResourceNode* podRezNode = [CC3PODResourceNode nodeWithName: #"FirstModel"];
podRezNode.resource = [IntroducingPODResource resourceFromFile: #"FirstModel.pod"];
podRezNode.shouldCullBackFaces = NO;
podRezNode.location = cc3v(0.0, -10.0, 0.2);
podRezNode.isTouchEnabled = YES;
[self addChild: podRezNode];
[NSThread detachNewThreadSelector:#selector(loadScenesInThread) toTarget:self
withObject:nil];
}
// Crashing if i add the below function in separate thread or background thread
-(void) loadScenesInThread
{
CC3PODResourceNode* podRezNode = [CC3PODResourceNode nodeWithName: #"SecondModel"];
podRezNode.resource = [IntroducingPODResource resourceFromFile: #"SecondModel.pod"];
podRezNode.shouldCullBackFaces = NO;
podRezNode.location = cc3v(0.0, -10.0, -5.0);
podRezNode.isTouchEnabled = YES;
[self addChild: podRezNode];
podRezNode = [CC3PODResourceNode nodeWithName: #"ThirdModel"];
podRezNode.resource = [IntroducingPODResource resourceFromFile: #"ThirdModel.pod"];
podRezNode.shouldCullBackFaces = NO;
podRezNode.location = cc3v(0.0, -10.0, -5.0);
podRezNode.isTouchEnabled = YES;
[self addChild: podRezNode];
// .. do more
}
Could someone guide me how would i handle such situation?
You can not create or access OpenGL resources in a thread that's different from the thread the OpenGL context was created on. Your only option is to use any "async" method cocos2d offers, or example for loading textures.

NSURLResponse Content-Length mismatch size of actual data when running concurrent downloads

I have a UITableView, when a cell is selected, I make a service call to asynchronously download a PDF file from a web service. It works great, until you select multiple cells directly after one-another (individually), then things start going south..
Here's some (stripped down) code to clarify:
Inside MasterViewController.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//To uniquely identify this field, I build a simple string using the indexPath.
NSString *key = [[NSString alloc]initWithFormat:#"%d.%d",pIndex.section,pIndex.row];
//Use a pre-populated NSDictionary to specify the file I want from the server.
NSString *reportID = [reportDictionary valueForKey:key];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/RequestReportWithID",myConnectionObject.ServiceURL]];
NSMutableDictionary *body = [[NSMutableDictionary alloc] initWithObjectsAndKeys:reportID, #"reportID", nil];
NSData *requestData = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%d", [requestData length]] forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody: requestData];
//Create a new object as delegate to receive the data, also add the key to assist with identification.
ReportDownloader *newReportDownloader = [[ReportDownloader alloc]initWithDelegate:self andKey:key];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:newReportDownloader];
if (connection)
{
//Nothing to do here, request was made.
}
}
Inside ReportDownloader.m:
long long expectedReportSize;
NSString *key;
NSData *thisReport;
-(NSObject*)initWithDelegate:(NSObject*)pDelegate andKey:(NSString*)pKey
{
myTypedDelegate = (MasterViewController*)pDelegate;
Key = pKey;
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
expectedReportSize = [response expectedContentLength];
NSLog(#"Key:%#, Expected Size=%lld bytes",Key, [response expectedContentLength]);
thisReport = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[thisReport appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//Here is where things go wrong...
NSLog(#"Key:%#, Actual Size=%ld bytes",Key, (unsigned long)[thisReport length]);
}
The above code works fine for a single report. And on a fast line, it works fine for 3-4 reports, but try to do more than that and the Expected length starts mismatching the Actual length. And I can't understand whyyyyy....
See the output generated by the code above:
(Notice that only the report for indexPath 1.1 was downloaded correctly)
Key:1.0, Expected Size=304006 bytes
Key:1.3, Expected Size=124922 bytes
Key:1.0, Actual Size=369494 bytes
Key:1.3, Actual Size=380030 bytes
Key:1.2, Expected Size=179840 bytes
Key:1.4, Expected Size=377046 bytes
Key:1.2, Actual Size=114376 bytes
Key:1.5, Expected Size=175633 bytes
Key:1.4, Actual Size=180558 bytes
Key:1.5, Actual Size=274549 bytes
Key:1.1, Expected Size=443135 bytes
Key:1.1, Actual Size=443135 bytes
The data corruption is confirmed when trying to open the PDF documents, which fail for all file's who's data size didn't match the expectedContentLength.
I am completely baffled by this behaviour, I'm hoping that I'm making an obvious n00b mistake and that someone here spots it.
The main reason WHY I made the ReportDownloader class was to avoid problems like this one.
Thanks in advance to whoever takes the time to scan my code!
Ok, the solution to this problem can be found here:
https://stackoverflow.com/a/1938395/1014983
This answer by James Wald, although not the question's accepted answer, completely solved all my problems with multiple simultaneous async NSURLConnections.
Let me know if you need some help with the implementation, but it's pretty straight forward.

Using AVAudioPlayer in iOS5?

So I'm trying to play a .wave file in iOS5, and I'm getting a warning, which is leading to SIGABRTs and generally not-working code.
NSURL *soundUrl = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/Resources/Sounds/01_main_menu_list.wav", [[NSBundle mainBundle] resourcePath]]];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
[_audioPlayer setDelegate:self];
[_audioPlayer play];
This is giving me an error "Sending "ViewController *const __strong" to parameter of incompatible type 'id '.
I have literally no idea why, I've followed a half-dozen examples and am coming up empty. I'd love a pointer in the right direction.
This should work:
NSURL* soundUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"01_main_menu_list" ofType:#"wav"]];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
audioPlayer.delegate = self;
[audioPlayer play];
If not, I think there's something wrong with the wav file. Maybe the encoding?
Turned out it was an issue with ARC releasing, I fixed it by adding a #property and #synthesize pair for the AVAudioPlayer in question, and declaring it as strong. It got rid of all of these errors, and played the file with no problems.

Resources