CoreGraphics drawRect in a cocos2d project EXEC BAD ACCESS - uikit

I got a interesting riddle here. My cocos2d project uses a UIView to display nicer popups than the regular alert view. To be more flexible in the size of the popup, I draw the background in the drawRect method.
But first the hierarchy how I implement cocos2d and UIKit:
Every UIKit element is added to the RootViewController. Every CCNode to the EAGLView. (If someone has a better solution to mix those world, don't wait to tell me!) Unfortunately is every UIKit view in front of cocos nodes.
Everything works fine when I add the first popup to the RootViewController. But if I remove the first popup and add a new one to the RootViewController occurs a bad access.
It crashes only in combination with cocos2d.
I use the code from Ray Wenderlichs CoreGraphics 101 tutorial.
context and strokeColor are not nil.
Another important infos: I use ARC and am supporting iOS 4.2 and above.
The complete code can be found at raywenderlich.com or below
void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor)
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = { 0.0, 1.0 };
NSArray *colors = [NSArray arrayWithObjects:(__bridge id)startColor, (__bridge id)endColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,
(__bridge CFArrayRef) colors, locations);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
CGRect rectFor1PxStroke(CGRect rect)
{
return CGRectMake(rect.origin.x + 0.5, rect.origin.y + 0.5, rect.size.width - 1, rect.size.height -1);
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect frame = CGRectInset(self.bounds, 2, 2);
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 3.0, shadowColor);
CGRect strokeRect = CGRectInset(frame, -2.0, -2.0);
CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextSetLineWidth(context, 2.0);
CGContextStrokeRect(context, rectFor1PxStroke(strokeRect));
drawLinearGradient(context, frame, gradient1, gradient2);
}

ARC releases the CGColorRef named strokeColor. One fix is to replace it with an CGFloat array and use CGContextSetStrokeColor instead CGContextSetStrokeColorWithColor
This answer solves this issue:
App crashes when using __bridge for CoreGraphics gradient on ARC

I agree with zeiteisen ,ARC releases CGColorRef and the easiest way to solve this by
replacing CGContextSetStrokeColorWithColor with
CGContextSetStrokeColor(context, components)
Change UIColor of strokeColor to 'RGB CGFloat components array' as following:
static CGFloat red, green, blue, alpha;
- (void)getRGBComponents:(CGFloat [4])components forColor:(UIColor *)color {
[color getRed:&red green:&green blue:&blue alpha:&alpha];//To fetch CGFloat RGB components
for (int component = 0; component < 4; component++) {
switch (component) {
case 0:
components[component] = red;
break;
case 1:
components[component] = green;
break;
case 2:
components[component] = blue;
break;
case 3:
components[component] = alpha;
break;
default:
break;
}
}
}
Use this method like this:
CGFloat components[4];
UIColor *strokeColor=[UIColor greyColor];//My color
[self getRGBComponents:components forColor:strokeColor];//'components' will be substituted in CGContextSetStrokeColor
NSLog(#"%f %f %f %f", components[0], components[1], components[2],components[3]);

Related

Detecting and ignoring touch on non-transparent part of MKOverlay drawn Image in iOS8

I have a overlay image (.png) on my map that consists of a transparent bit in the middle, and colored sides so the user can only focus on the middle part. However do to the shape of that middle bit, quite a bit is visible at some sides.
I'm trying to detect a tap on the OverlayView so I can ignore it and only accept touches in the designated area.
I followed the following tut at Ray Wenderlich's site for adding the overlay:
The image overlay is drawn like this:
#implementation PVParkOverlayView
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage {
self = [super initWithOverlay:overlay];
if (self) {
_overlayImage = overlayImage;
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGImageRef imageReference = self.overlayImage.CGImage;
//UIImage *imageTest = _overlayImage;
MKMapRect theMapRect = self.overlay.boundingMapRect;
CGRect theRect = [self rectForMapRect:theMapRect];
//orientation testing
//CGContextRotateCTM(context, 0);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, -theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);
}
I have a gesture recognizer on my mapview and am trying to detect the tap there:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint tapPoint = [gestureRecognizer locationInView:self.mapView];
CLLocationCoordinate2D tapCoord = [self.mapView convertPoint:tapPoint toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(tapCoord);
CGPoint mapPointAsCGP = CGPointMake(mapPoint.x, mapPoint.y);
for (id<MKOverlay> overlay in self.mapView.overlays) {
if([overlay isKindOfClass:[PVParkOverlay class]]){
NSLog(#"overlay is present");
/*
MKPolygon *polygon = (MKPolygon*) overlay;
CGMutablePathRef mpr = CGPathCreateMutable();
MKMapPoint *polygonPoints = polygon.points;
for (int p=0; p < polygon.pointCount; p++){
MKMapPoint mp = polygonPoints[p];
if (p == 0)
CGPathMoveToPoint(mpr, NULL, mp.x, mp.y);
else
CGPathAddLineToPoint(mpr, NULL, mp.x, mp.y);
}
if(CGPathContainsPoint(mpr , NULL, mapPointAsCGP, FALSE)){
// ... found it!
NSLog(#"I've found it!");
}
//CGPathRelease(mpr);
*/
}
}
I know that the overlay is there, but since it is a drawn image I can't find a way to convert this to polygon points to use this code (if even possible).
Any other methods I can use for this?
I also found following sample code but the viewForOverlay method is deprecated:
- (void)mapTapped:(UITapGestureRecognizer *)recognizer
{
MKMapView *mapView = (MKMapView *)recognizer.view;
id<MKOverlay> tappedOverlay = nil;
for (id<MKOverlay> overlay in mapView.overlays)
{
MKOverlayView *view = [mapView viewForOverlay:overlay];
if (view)
{
// Get view frame rect in the mapView's coordinate system
CGRect viewFrameInMapView = [view.superview convertRect:view.frame toView:mapView];
// Get touch point in the mapView's coordinate system
CGPoint point = [recognizer locationInView:mapView];
// Check if the touch is within the view bounds
if (CGRectContainsPoint(viewFrameInMapView, point))
{
tappedOverlay = overlay;
break;
}
}
}
NSLog(#"Tapped view: %#", [mapView viewForOverlay:tappedOverlay]);
}

Double tap to Zoom with UIScrollView

I am using Apple's source code. I am sure there is just a snippet that I have messed up. I have a form that I have made programmatically. When I double tap the recognizer fires and goes through the motions but I it won't actually zoom. Any suggestions? Thanks for the help
imageScrollView.delegate=self;
CGRect fullScreenRect=[[UIScreen mainScreen] applicationFrame];
imageScrollView=[[UIScrollView alloc]initWithFrame:fullScreenRect];
imageScrollView.contentSize=CGSizeMake(750,1250);
imageScrollView.minimumZoomScale=1.0;
imageScrollView.maximumZoomScale=5.0;
[imageScrollView setZoomScale:imageScrollView.minimumZoomScale];
self.view=imageScrollView;
[imageScrollView release];
Added a bunch of UItextfields/labels to the scrollview.
aircraftType = [[UITextField alloc]initWithFrame:CGRectMake(170, 32, 150, 15)];
aircraftType.font=[UIFont systemFontOfSize:10];
aircraftType.borderStyle = UITextBorderStyleRoundedRect;
aircraftType.placeholder = #"Aicraft Type";
aircraftType.delegate = self;
[self.view addSubview:aircraftType];
#pragma mark UIScrollViewDelegate methods
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}
#pragma mark TapDetectingImageViewDelegate methods
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer {
// double tap zooms in
float newScale = [imageScrollView zoomScale] * ZOOM_STEP;
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:gestureRecognizer.view]];
[imageScrollView zoomToRect:zoomRect animated:YES];
}
#pragma mark Utility methods
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
// the zoom rect is in the content view's coordinates.
// At a zoom scale of 1.0, it would be the size of the imageScrollView's bounds.
// As the zoom scale decreases, so more content is visible, the size of the rect grows.
zoomRect.size.height = [imageScrollView frame].size.height / scale;
zoomRect.size.width = [imageScrollView frame].size.width / scale;
// choose an origin so as to get the right center.
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}

CCRenderTexture and threads warning on OS X

I am getting this warning with Cocos2D 2.0 on OS X:
-[CCRenderTexture initWithWidth:height:pixelFormat:depthStencilFormat:] : cocos2d: WARNING. CCRenderTexture is running on its own thread. Make sure that an OpenGL context is being used on this thread!
Here's the code that I believe is causing the warning:
- (id) initWithObject: (CCNode *) object mask: (CCSprite *) mask {
NSAssert(object != nil, #"Invalid sprite for object");
NSAssert(mask != nil, #"Invalid sprite for mask");
if((self = [super init])) {
_objectSprite = object;
_maskSprite = mask;
// Set up the burn sprite that will "knock out" parts of the darkness layer depending on the alpha value of the pixels in the image.
[_maskSprite setBlendFunc: (ccBlendFunc) { GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }];
// Get window size, we want masking over entire screen don't we?
CGSize size = [[CCDirector sharedDirector] winSize];
// Create point with middle of screen
CGPoint screenMid = ccp(size.width * 0.5f, size.height * 0.5f);
// Create the rendureTextures for the mask
_masked = [CCRenderTexture renderTextureWithWidth: size.width height: size.height];
[[_masked sprite] setBlendFunc: (ccBlendFunc) { GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA }];
// Set render textures at middle of screen
_masked.position = screenMid;
// Add the masked object to the screen
[self addChild: _masked];
[[CCDirector sharedDirector] setAlphaBlending:YES];
}
return self;
}
Please help, I just cannot figure this out.
In ccConfig.h, find a line that says
#define CC_DIRECTOR_MAC_THREAD CC_MAC_USE_DISPLAY_LINK_THREAD
This is where the use of threads is defined. You can try using the main thread option (CC_MAC_USE_MAIN_THREAD), or use GCD to make sure all code is actually executed on -[CCDirector runningThread]. This will pretty much fix it.

cocos2d create a circle shape and use it as a sprite

so I would like to create a circle shape with primitives on cocos2d and then use it as a sprite, How can I do it please ?
I know that I have to use something like this :
glLineWidth(16);
glColor4ub(0, 255, 0, 255);
drawCircle( ccp(s.width/2, s.height/2), 100, 0, 10, NO);
But it's hard for me to understand how it works and How to use it as a sprite
do you really need CCSprite instance? you can create a subclass of CCNode, then in its
- (void) draw
method put your code there. your circle will have it's center position (0.f, 0.f)
#implementation MyScene
- (void) onEnter
{
[super onEnter];
CCNode* myNode = [MyNodeSubclass node];
[node setPosition: someRandomPosition ];
[self addChild: node];
}
#end
#implementation MyNodeSubclass
- (void) draw
{
glColor4f(255, 255, 255, 255);
CCPoint center = ccp(0.f, 0.f);
CGFloat radius = 10.f;
CGFloat angle = 0.f;
NSInteger segments = 10;
BOOL drawLineToCenter = YES;
ccDrawCircle(center, radius, angle, segments, drawLineToCenter);
}
#end
wrote this piece of code right here, didn't copy from xcode, but it should work as you want. ccDrawCircle is a cocos2d function, declared in CCDrawingPrimitives.h

Creating an animatable translucent overlay with Core Animation layers

I'm creating a spotlight that moves over content in my app, like so:
In the sample app (shown above), the background layer is blue, and I have a layer over it that darkens all of it, except a circle that shows it normally. I've got this working (you can see how in the code below). In my real app, there is actual content in other CALayers, rather than just blue.
Here's my problem: it doesn't animate. I'm using CGContext drawing to create the circle (which is an empty spot in an otherwise black layer). When you click the button in my sample app, I draw the circle at a different size in a different location.
I would like that to smoothly translate and scale, instead of jumping, as it currently does. It may require a different method of creating the spotlight effect, or there might be a way I don't know of to implicitly animate the -drawLayer:inContext: call.
It's easy to create the sample app:
Make a new Cocoa app (using ARC)
Add the Quartz framework
Drop a custom view and a button onto the XIB
Link the custom view to a new class (SpotlightView), with code provided below
Delete SpotlightView.h, since I included its contents in SpotlightView.m
Set the button's outlet to the -moveSpotlight: action
Update (the mask property)
I like David Rönnqvist's suggestion in comments to use the mask property of the darkened layer to cut out a hole, which I could then move independently. The problem is that for some reason, the mask property works the opposite of how I expect a mask to work. When I specify a circular mask, all that shows up is the circle. I expected the mask to work in the opposite manner, masking out the area with 0 alpha.
Masking feels like the right way to go about this, but if I have to fill in the entire layer and cut out a hole, then I may as well do it the way I originally posted. Does anyone know how to invert the -[CALayer mask] property, so that the area drawn in gets cut out from the layer's image?
/Update
Here's the code for SpotlightView:
//
// SpotlightView.m
//
#import <Quartz/Quartz.h>
#interface SpotlightView : NSView
- (IBAction)moveSpotlight:(id)sender;
#end
#interface SpotlightView ()
#property (strong) CALayer *spotlightLayer;
#property (assign) CGRect highlightRect;
#end
#implementation SpotlightView
#synthesize spotlightLayer;
#synthesize highlightRect;
- (id)initWithFrame:(NSRect)frame {
if ((self = [super initWithFrame:frame])) {
self.wantsLayer = YES;
self.highlightRect = CGRectNull;
self.spotlightLayer = [CALayer layer];
self.spotlightLayer.frame = CGRectInset(self.layer.bounds, -50, -50);
self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
self.spotlightLayer.opacity = 0.60;
self.spotlightLayer.delegate = self;
CIFilter *blurFilter = [CIFilter filterWithName:#"CIGaussianBlur"];
[blurFilter setValue:[NSNumber numberWithFloat:5.0]
forKey:#"inputRadius"];
self.spotlightLayer.filters = [NSArray arrayWithObject:blurFilter];
[self.layer addSublayer:self.spotlightLayer];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {}
- (void)moveSpotlight:(id)sender {
[self.spotlightLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
if (layer == self.spotlightLayer) {
CGContextSaveGState(ctx);
CGColorRef blackColor = CGColorCreateGenericGray(0.0, 1.0);
CGContextSetFillColorWithColor(ctx, blackColor);
CGColorRelease(blackColor);
CGContextClearRect(ctx, layer.bounds);
CGContextFillRect(ctx, layer.bounds);
// Causes the toggling
if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) {
self.highlightRect = CGRectMake(25, 25, 100, 100);
} else {
self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50,
NSMaxY(self.layer.bounds) - 50,
25, 25);
}
CGRect drawnRect = [layer convertRect:self.highlightRect
fromLayer:self.layer];
CGMutablePathRef highlightPath = CGPathCreateMutable();
CGPathAddEllipseInRect(highlightPath, NULL, drawnRect);
CGContextAddPath(ctx, highlightPath);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGContextFillPath(ctx);
CGPathRelease(highlightPath);
CGContextRestoreGState(ctx);
}
else {
CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0);
CGContextSetFillColorWithColor(ctx, blueColor);
CGContextFillRect(ctx, layer.bounds);
CGColorRelease(blueColor);
}
}
#end
I finally got it. What prodded me to the answer was hearing of the CAShapeLayer class. At first, I thought it would be a simpler way to draw the layer's contents, rather than drawing and clearing the contents of a standard CALayer. But I read the documentation of the path property of CAShapeLayer, which stated it could be animated, but not implicitly.
While a layer mask might have been more intuitive and elegant, it doesn't seem to be possible to use the mask to hide a portion of the owner's layer, rather than showing a portion, and so I couldn't use it. I'm happy with this solution, as it's pretty clear what's going on. I wish it used implicit animation, but the animation code is only a few lines.
Below, I've modified the sample code from the question to add smooth animation. (I removed the CIFilter code, because it was extraneous. The solution does still work with filters.)
//
// SpotlightView.m
//
#import <Quartz/Quartz.h>
#interface SpotlightView : NSView
- (IBAction)moveSpotlight:(id)sender;
#end
#interface SpotlightView ()
#property (strong) CAShapeLayer *spotlightLayer;
#property (assign) CGRect highlightRect;
#end
#implementation SpotlightView
#synthesize spotlightLayer;
#synthesize highlightRect;
- (id)initWithFrame:(NSRect)frame {
if ((self = [super initWithFrame:frame])) {
self.wantsLayer = YES;
self.highlightRect = CGRectNull;
self.spotlightLayer = [CAShapeLayer layer];
self.spotlightLayer.frame = self.layer.bounds;
self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
self.spotlightLayer.fillRule = kCAFillRuleEvenOdd;
CGColorRef blackoutColor = CGColorCreateGenericGray(0.0, 0.60);
self.spotlightLayer.fillColor = blackoutColor;
CGColorRelease(blackoutColor);
[self.layer addSublayer:self.spotlightLayer];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {}
- (CGPathRef)newSpotlightPathInRect:(CGRect)containerRect
withHighlight:(CGRect)spotlightRect {
CGMutablePathRef shape = CGPathCreateMutable();
CGPathAddRect(shape, NULL, containerRect);
if (!CGRectIsNull(spotlightRect)) {
CGPathAddEllipseInRect(shape, NULL, spotlightRect);
}
return shape;
}
- (void)moveSpotlight {
CGPathRef toShape = [self newSpotlightPathInRect:self.spotlightLayer.bounds
withHighlight:self.highlightRect];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"path"];
pathAnimation.fromValue = (__bridge id)self.spotlightLayer.path;
pathAnimation.toValue = (__bridge id)toShape;
[self.spotlightLayer addAnimation:pathAnimation forKey:#"path"];
self.spotlightLayer.path = toShape;
CGPathRelease(toShape);
}
- (void)moveSpotlight:(id)sender {
if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) {
self.highlightRect = CGRectMake(25, 25, 100, 100);
} else {
self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50,
NSMaxY(self.layer.bounds) - 50,
25, 25);
}
[self moveSpotlight];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0);
CGContextSetFillColorWithColor(ctx, blueColor);
CGContextFillRect(ctx, layer.bounds);
CGColorRelease(blueColor);
}
#end

Resources