My NSOpenGLView won't work. I've tried everything - objective-c

I want to display something in an NSOpenGLView, but since there is a total of zero bytes of documentation on it, and the sample code is as big and complex as the documentation should be, I can't get any wise out of it. This is my code so far, and my ANOpenGLView is an NSOpenGLView in a NIB subclassed as ANOpenGLView:
#implementation ANOpenGLView
#synthesize animationTimer;
// MEM
- (void)dealloc {
[animationTimer release];
[super dealloc];
}
// INIT
- (id)initWithFrame:(NSRect)frameRect {
if (self = [super initWithFrame:frameRect]) {
NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = {
NSOpenGLPFADoubleBuffer,
NSOpenGLPFADepthSize, 32,
0
};
NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes];
[self setOpenGLContext:[[[NSOpenGLContext alloc] initWithFormat:format shareContext:nil] autorelease]];
}
return self;
}
- (void)awakeFromNib {
/* 60 FPS */
animationTimer = [[NSTimer timerWithTimeInterval:(1.0f/60.0f) target:self selector:#selector(redraw:) userInfo:nil repeats:YES] retain];
[[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSDefaultRunLoopMode];
}
// DRAW
- (void)redraw:(NSTimer*)theTimer {
[self drawRect:[self bounds]];
}
- (void)drawRect:(NSRect)dirtyRect {
NSLog(#"Redraw");
[[self openGLContext] clearDrawable];
[[self openGLContext] setView:self];
[[self openGLContext] makeCurrentContext];
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glDisable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glViewport(0, 0, [self frame].size.width, [self frame].size.height);
glMatrixMode(GL_PROJECTION); glLoadIdentity();
glMatrixMode(GL_MODELVIEW); glLoadIdentity();
glTranslatef(-1.5f, 0.0f, -6.0f);
glBegin( GL_TRIANGLES );
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f(-1.0f, -1.0f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(1.0f, -1.0f);
glEnd();
[[self openGLContext] flushBuffer];
[NSOpenGLContext clearCurrentContext];
}
#end
How can I get the triangle to show up? The only thing I get is a blank, white screen.
P.S. I want to draw in 2D.
EDIT I have updated my code, and this is what I have now:

I'm not sure that it's the only problem, but:
You haven't defined the pixel format
You haven't set up the matrices in the code
You haven't set up the viewport
Here http://www.cocoadev.com/index.pl?NSOpenGLView is a short example, it's almost what you need, but there is perspective matrix set up while you need orthogonal to render in 2D space (function glOrtho). World & View can be identity in this case.
Since 2D is your target, you won't deal with this matrices too much, just set them once.

For one thing, you've requested a double buffered context but you never swap them, so you're probably always drawing into the unseen back buffer. Add a call to glSwapAPPLE() after you flush.
I'm also a bit unsure about all your direct [self openGLContext] calls. Normally NSOpenGLView ought to handle this stuff for you, but in this case you're bypassing the set up of the view by calling drawRect directly from your timer, so things might be a bit messed up.
Have you tried just calling [self setNeedsDisplay:YES] from your timer method and letting the normal view setup occur? I would expect the redrawing still to be fast enough that way and it would make life easier...

Edit: #Peter is right, Still a great resource, just not applicable to this question.
Jeff Lamarche's openGL posts are a great introduction and they include a project template that handles most of the setup for you.
http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-table-of.html

Related

EXC_BAD_ACCESS while addingAnnotations in MKMapView

I am calling a web service that returns an array of annotations in a delegate method that I am adding to my map with the addAnnotations method for a MKMapView. Everything goes swimmingly until the delegate method send two arrays in quick succession (usually about 150ms - 500ms) and then I get a EXC_BAD_ACCESS (code=1 address 0x20) on this line [kMap addAnnotations:tileArray]; - This appears to be a memory issue but i am not really sure what to do how it or how to change my code to address it.
Here is the delegate method
-(void)rebelBaseManager:(RebelBaseManager *)manager didUpdateTileHour:(NSArray *)tileArray boundaryBreak:(NSString *)breakType atTileLevel:(int)callTileLevel {
if (timerStarted == NO) {
[self startTimer];
}
//Check for tileLevel in case multiple calls were made at different tile levels
if (tileLevel == callTileLevel) {
[kMap addAnnotations:tileArray];
[HourInMap addObjectsFromArray:tileArray];
}
}
I also added a method to allow me to animate the removal of annotations which is below in case it makes a difference:
- (void)removeAnnotationsWithFade:(NSArray *)annotations animated:(BOOL)shouldAnimate {
if (!shouldAnimate)
[self removeAnnotations:annotations];
else {
for (HourAnnotation *annotation in annotations) {
MKAnnotationView *annotationView = [self viewForAnnotation:annotation];
[UIView animateWithDuration:2
animations:^{
annotationView.alpha =0;
}completion:^(BOOL finished) {
[self removeAnnotation:annotation];
}];
}
}
--------- ADDITION ---------
Adding in my code from a custom annotation in response to #3 in Rob's answer below.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (id)initWithHourAnnotation:(HourAnnotation *)hourAnnotation reuseIdentifier:(NSString *)reuseIdentifier {
CGRect myFrame = self.frame;
myFrame.size.width = hourAnnotation.frameSize;
myFrame.size.height = hourAnnotation.frameSize;
self = [super initWithFrame:myFrame];
//When I use this here I seem to get the frame and color of the old annotation displayed
//self = [super initWithAnnotation:velocityAnnotation reuseIdentifier:reuseIdentifier];
if (self)
{
self.layer.cornerRadius = self.frame.size.width / 2;
self.clipsToBounds = YES;
[self setBackgroundColor:[UIColor clearColor]];
NSArray *alphaValue = [[NSArray alloc]initWithArray:[self alphaForTileLevel]];
self.fillColor = [UIColor colorWithHue:hourAnnotation.color saturation:.06 brightness:.23 alpha:[[alphaValue objectAtIndex:hourAnnotation.tileLevel-1]doubleValue]];
self.strokeColor = [UIColor colorWithHue:hourAnnotation.color saturation:.06 brightness:.23 alpha:.35];
self.enabled = NO;
self.canShowCallout = NO;
self.userInteractionEnabled = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
[self.fillColor set];
[path fill];
[self.strokeColor set];
[path setLineWidth:2.0f];
[path stroke];
}
A couple of thoughts.
You're not showing where you are calling this removeAnnotationsWithFade. We have to assume that you're removing the appropriate model objects. use instruments to confirm that you're not leaking anywhere.
If the app is crashing when removing with fade, but not crashing without fade, then you might consider refactoring this code. Specifically, your animation code isn't actually removing the old animations until the completion block (i.e. two seconds later). Thus you are holding on to old annotations longer than needed. And if the annotations are coming in fast and furious, you could run into memory problems.
If you, for example, used transitionWithView:mapView instead, just removing the annotations directly, you might be freeing memory associated with the old annotations more quickly. It's a less elegant animation, but might be "good enough" and minimize your peak memory usage:
// this will immediately remove the annotations, but animate the fading of the transition
[UIView transitionWithView:self.mapView duration:2.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[self.mapView removeAnnotations:annotations];
} completion:nil];
// presumably remove the annotations from your array, too
Is your viewForAnnotation dequeuing old annotation views and only instantiating new ones if it couldn't dequeue an old one, or is it always instantiating new ones? Stuff like that might help control peak memory usage, too.

UITapGesture only works after reLoad UIViewController

I have 1 year works with Objective-C and is my first question that I make because I always found the answer in this site, but this situation is completely insane and did not found anything similar.
I have a custom class (called BallClass from UIView). The UIView have an UIImageView inside on it and a UITapGesture to make an action.
So when I create the object in a UIViewController, does not works the tapAction when press in the object, but if I change to other screen and return it, the tapGesture works perfect.
I tried many options, but I cant makes work in the first load.
I work with ARC, Xcode (4.6.3) and OSX (10.8.4) and IB but this objects was created with no IB .
Thanks in advance.
Here is the class BallClass.m
- (void)tapGesture:(UIGestureRecognizer *)tapGesture {
NSLog(#"hola");
}
- (id)initBall:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
// Recibir las variables iniciales para el control del timer
ball = [[Ball alloc] init];
// Agregar la imagen de la pelota en el view
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height)];
[imageView setCenter:CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds]))];
[imageView setContentMode:UIViewContentModeScaleAspectFit];
[imageView setImage:[UIImage imageNamed:#"pelota.png"]];
[imageView setTag:100];
[imageView setNeedsDisplay];
// Add TAP
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
// Tap Properties
[tapGesture setDelaysTouchesBegan:YES];
[tapGesture setNumberOfTapsRequired:1];
[tapGesture setDelegate:(id)self];
[imageView addGestureRecognizer:tapGesture];
[self addSubview:imageView];
[self setNeedsDisplay];
// Propiedades del View
[self setUserInteractionEnabled:YES];
self.backgroundColor = [UIColor clearColor];
[self setTag:100];
// BALL BOUNCE
[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
return self;
}
- (void)onTimer:(NSTimer *)timerLocal {
// Do something
}
... // more methods
Here is the instantiate:
ballView00 = [[BallView alloc] initBall:CGRectMake(10, 10, 40, 40)];
The UiView show perfect, and the animation works fine, but the UITapGesture just works, as mentioned early, after reLoad the ViewController.
Here is the link to image the Ball and UIView (#kBall). Sorry, about image, but I cant load until I have 10 points :(
This might be an issue with the superview's frame. What view is holding ballView00? Does ballView lie entirely within it's superview's bounds?
Usually when you encounter problem where you're expecting a touch but don't get it it's because the view set to receive the touch is outside one of its parents' bounds.
So if the superview bounds are 100x100 and the ball is at x=125 y=125 it will not receive a touch.
A lot of magic happens when your views get laid out, it's not surprising to sometimes find one with 0x0 dimensions because of some incorrect constraints.
You can easily check the bounds of all your views by giving them a background color.
I found the solution.
The ball make a spin with this:
[UIView animateWithDuration: 0.5f
delay: 0.0f
options: UIViewAnimationOptionCurveEaseIn
animations: ^{
self.transform = CGAffineTransformRotate(self.transform, M_PI / 2);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
}
}
}];
And generate conflict with the main timer. I change the spin action with simple add self.transform = CGAffineTransformRotate(self.transform, M_PI / 2); inside drawRect method in the class.
Apparently, I can't nested two timers, because "disable" the tapGesture, so when I change of the screen and return, the spin was stopped and this help to found this situation.
Thanks for your help.

Receive global TouchEvents with NSEvent

I just try to receive Touch Events globally in the Window and not only in a view.
In my code, you can see below, i will get the absolute position of the touch in the magic trackpad. As long as the cursor is inside the view (the red NSRect) it works fine, but how can i receive touches outside of this view.
I searched for solutions in many communities and the apple devcenter but found nothing.
I think the problem is this: NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseTouching inView:nil]; Isn't there a method in NSEvent that gets every touch?
Hope anybody can help me.
Here my Implementation:
#implementation MyView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
[self setAcceptsTouchEvents:YES];
myColor = [NSColor colorWithDeviceRed:1.0 green:0 blue:0 alpha:0.5];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
NSRect bounds = [self bounds];
[myColor set];
[NSBezierPath fillRect:bounds];
}
- (void)touchesBeganWithEvent:(NSEvent *)ev {
NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseTouching inView:nil];
for (NSTouch *touch in touches) {
NSPoint fraction = touch.normalizedPosition;
NSSize whole = touch.deviceSize;
NSPoint wholeInches = {whole.width / 72.0, whole.height / 72.0};
NSPoint pos = wholeInches;
pos.x *= fraction.x;
pos.y *= fraction.y;
NSLog(#"%s: Finger is touching %g inches right and %g inches up "
#"from lower left corner of trackpad.", __func__, pos.x, pos.y);
}
}
Calling touchesMatchingPhase:inView: with nil for the last parameter (the way you are doing) will get all touches. The problem is that touchesBeganWithEvent: will simply not fire for a control that isn't in the touch area.
You can make the view the first responder, which will send all events to it first. See becomeFirstResponder and Responder object

CGAffineTransformMakeScale Makes UIView Jump to Original Size before scale

I have a UIView that I set up to respond to pinch gestures and change its size, except, once you enlarge it and then try and pinch it again, it jumps to its original size (which just so happens to be 100x200). Here is the code:
#implementation ChartAxisView
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// do gesture recognizers
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(onPinch:)];
[self addGestureRecognizer:pinch];
[pinch release];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0].CGColor;
CGContextSetFillColorWithColor(context, redColor);
CGContextFillRect(context, self.bounds);
}
- (void)onPinch: (UIPinchGestureRecognizer*) gesture {
self.transform = CGAffineTransformMakeScale(gesture.scale, gesture.scale);
}
- (void)dealloc {
[super dealloc];
}
#end
Any thoughts?
So there are two types of Scale (or, transform in general) functions: CGAffineTransformMakeScale and CGAffineTransformScale
The first one, CGAffineTransformMakeScale which you are using, always transforms with respect to the image's original size. And that is why you see the jump to its original size before the scaling happens.
The second one, CGAffineTransformScale, transforms from the image's current position. This is what you need. For this, it requires an additional 'transform' arg. The 'transform' arg in your case represents the enlarged image.
Read this very informative blog post about transformations.
- (void)onPinch: (UIPinchGestureRecognizer*) gesture {
if ([gesture state] == UIGestureRecognizerStateBegan)
{
curTransform = self.transform;
}
self.transform = CGAffineTransformScale(curTransform,gesture.scale, gesture.scale);
}
use CGAffineTransformScale instead of CGAffineTransformMakeScale
you will need a member -> CGAffineTransform curTransform;
;)
you can set transform with following code:
ivClone setTransform:CGAffineTransformMakeScale(scale, scale)];
and you can get current transform with following coee:
newV.transform.a //x scale
newV.transform.d // y scale

Stop drawing of CATiledLayer

Is is possible to stop CATiledLayer to draw (drawLayer:inContext)?
It draws asynchronously and when i try to release CGPDFDocumentRef, which is used by CATiledLayer, the app crashes (EXC_BAD_ACCESS).
That's my view:
#implementation TiledPDFView
- (id)initWithFrame:(CGRect)frame andScale:(CGFloat)scale{
if ((self = [super initWithFrame:frame])) {
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
tiledLayer.levelsOfDetail = 4;
tiledLayer.levelsOfDetailBias = 4;
tiledLayer.tileSize = CGSizeMake(512.0, 512.0);
myScale = scale;
}
return self;
}
// Set the layer's class to be CATiledLayer.
+ (Class)layerClass {
return [CATiledLayer class];
}
- (void)stopDrawing{
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
[tiledLayer removeFromSuperlayer];
tiledLayer.delegate = nil;
}
// Set the CGPDFPageRef for the view.
- (void)setPage:(CGPDFPageRef)newPage
{
CGPDFPageRelease(self->pdfPage);
self->pdfPage = CGPDFPageRetain(newPage);
//self->pdfPage = newPage;
}
-(void)drawRect:(CGRect)r
{
}
// Draw the CGPDFPageRef into the layer at the correct scale.
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
// First fill the background with white.
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,self.bounds);
CGContextSaveGState(context);
// Flip the context so that the PDF page is rendered
// right side up.
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Scale the context so that the PDF page is rendered
// at the correct size for the zoom level.
CGContextScaleCTM(context, myScale,myScale);
CGContextDrawPDFPage(context, pdfPage);
CGContextRestoreGState(context);
}
// Clean up.
- (void)dealloc {
CGPDFPageRelease(pdfPage);
[super dealloc];
}
And this is where i try to stop and release PDF in view controller:
v is instance of TiledPDFView
-(void) stopDwaring {
[v stopDrawing];
[v removeFromSuperview];
[v release];
[self.view removeFromSuperview];
self.view = nil;
CGPDFDocumentRelease(pdf);
}
this post helped me solving my own trouble with CATiledLayer. I used TiledPDFview.m from Apple's documentation as example.
Since I need to redraw the entire view and all tiles at some point, I use a CATiledLayer as property.
When exiting and deallocating the viewcontroller, it crashed with [CATiledLayer retain]: Message sent to deallocated instance.
Here is my dealloc method of the view controller:
- (void)dealloc {
self.tiledLayer.contents=nil;
self.tiledLayer.delegate=nil;
[self.tiledLayer removeFromSuperlayer];
// note: releasing the layer still crashes-
// I guess removeFromSuperlayer releases it already,
// but couldn't find documentation so far.
// So that's why it's commented out:
// [self.tiledLayer release], self.tiledLayer=nil;
//release the other viewcontroller stuff...
[super dealloc];
}
That works for me. Hope it helps someone.
Remove the CATiledLayer from its superlayer before releasing the CGPDFDocumentRef.
[yourTiledLayer removeFromSuperlayer];
Dont forget to set it's delegate to nil too.
yourTiledLayer.delegate = nil;
After that, you can safely release your CGPDFDocumentRef.
Edit after OP adds code:
Did you get pdfPage using CGPDFDocumentGetPage()? If so, you shouldn't release it, it is an autoreleased object.
Regarding how to add it as sublayer:
You don't actually need TiledPDFView. In your view controller, you can simply do this:
CATiledLayer *tiledLayer = [CATiledLayer layer];
tiledLayer.delegate = self; //sets where tiledLayer will look for drawLayer:inContext:
tiledLayer.tileSize = CGSizeMake(512.0f, 512.0f);
tiledLayer.levelsOfDetail = 4;
tiledLayer.levelsOfDetailBias = 4;
tiledLayer.frame = CGRectIntegral(CGRectMake(0.0f, 0.0f, 512.0f, 512.0f));
[self.view.layer addSublayer:tiledLayer];
Then move your drawLayer:inContext: implementation to your view controller.
Then in your view controller's dealloc, release it as:
[tiledLayer removeFromSuperlayer];
tiledLayer.delegate = nil;
CGPDFDocumentRelease(pdf);
Note that you can't do this on a UIView subclass, as the drawLayer:inContext: will conflict with the UIView's main layer.
object.layer.contents = Nil
This should wait for the thread to finish. It helped in my case.
TiledPDFView *pdfView;
In dealloc of pdfView's superview class, write below line of codes.
- (void)dealloc {
if (nil != self.pdfView.superview) {
self.pdfView.layer.delegate = nil;
[self.pdfView removeFromSuperview];
}
}
This works for me. Hope it will help.
I've had a similar problem.
I ended up setting a float variable "zoom" in my TiledPDFView which I set as the zoomScale of PDFScrollView in the UIScrollview Delegate method: scrollViewDidZoom
Then in my drawLayer method inside TiledPDFView I only called the contents of that method if the float variable "zoom" was above 2.
This fixes any issues of someone leaving the view without zooming. It may not be ideal for your case as this error still occurs if someone zooms above 2 then releases the viewcontroller quickly, but you might be able to find a similar technique to cover all bases.
It looks like you're doing the same thing I am, which is borrowing the ZoomingPDFView code and integrating it into your project. If your UIScrollView delegate methods in PDFScrollView are unchanged, you can solve your problem by just commenting both lines of your dealloc method (in TiledPDFView). That stuff should only be happening when you kill the parent view, anyway.