Setting mask on CALayer breaks hitTest - calayer

I have a CALayer with hitTesting that works fine. The code is something like this:
- (void)setup
{
_maskLayer = [CAShapeLayer layer];
_maskLayer.fillColor = [UIColor whiteColor].CGColor;
_maskLayer.path = somePath;
_theLayer.frame = CGRectMake(0, 0, size.width, size.height);
// Taps work fine if we do not apply the mask
// _theLayer.mask = _maskLayer;
}
- (IBAction)tapOnView:(UITapGestureRecognizer *)sender
{
CGPoint point = [sender locationInView:theView];
CALayer *subLayer = [_theLayer.presentationLayer hitTest:point].modelLayer;
if (subLayer != _theLayer && subLayer != nil) {
// Do something with the sublayer which the user touched
}
}
The problem is that if I uncomment the line with the mask, my hit testing always returns nil. Why does the mask kill hit testing? It appears correct visually.

It turns out that hit testing requires the point to be inside the bounds of the CALayer, but what is not documented is that it also has to be inside the bounds of the mask. So I changed my code like this and it worked:
_theLayer.frame = CGRectMake(0, 0, size.width, size.height);
// Apply a mask
_maskLayer.frame = _theLayer.frame;
_theLayer.mask = _maskLayer;
It took me a few hours to figure this out, so I wanted to share.

I used the accepted answer but still had some issue because you should use bounds just to prevent your mask not showing if your layer is other than 0,0 Also, this is the swift solution:
theLayer.frame = CGRecti(x: 0, y, 0: width: size.width, height:size.height)
maskLayer.frame = theLayer.bounds
theLayer.mask = maskLayer

Related

tvOS adjustsImageWhenAncestorFocused crops image collection view cell

When you use .imageView.adjustsImageWhenAncestorFocused you get your image scaled, you get it bigger on focus. The image goes beyond it's bounds. That means that you can't see full image - it's coped on all sides.
You have:
You want:
If you want it work from the box, you need to remember to reserve Focused/Safe zone size (from documentation) when you create your image.
In my case I have my images from server and I can't edit them.
What worked for me - it's to redraw image right before setting:
UIImage *oldImage = [UIImage imageNamed:#"example"];
CGFloat width = cell.imageView.frame.size.width;
CGFloat height = cell.imageView.frame.size.height;
CGSize newSize = CGSizeMake(width, height);
CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
CGImageRef imageRef = oldImage.CGImage;
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
CGContextRef resizeContext = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(resizeContext, kCGInterpolationHigh);
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height);
CGContextConcatCTM(resizeContext, flipVertical);
CGContextDrawImage(resizeContext, newRect, imageRef);
CGImageRef newImageRef = CGBitmapContextCreateImage(resizeContext);
UIImage *newImageResized = [UIImage imageWithCGImage:newImageRef];
CGImageRelease(newImageRef);
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = newImageResized;
});
It's nor necessary to redraw in this way, but the point is you need to draw your UIImage again in order to have your image displayed properly.
Code from here.

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]);
}

Auto-Resizing of NSTextView and/or NSScrollView

I have an NSTextView inside an NSView (which is being used by a NSPopover, don't know if that is relevant) that I'm trying to resize automatically and programmatically (cf caption).
I have been struggling with a lot of stuff, namely :
Looking at NSLayoutManager and usedRectForTextContainer that give me aberrant size values
(usedRectForTextContainer : {{0, 0}, {0.001, 28}})
Modifying NSScrollView frame, [NSScrollView contentView], [NSScrollView documentView]
Getting rid of AutoLayout
I reached the point where I can resize my scollview and my Popover, but I can't get the actual height of the text inside the NSTextView.
Any kind of help would be appreciated.
-(void)resize
{
//Get clipView and scrollView
NSClipView* clip = [popoverTextView superview];
NSScrollView* scroll = [clip superview];
//First, have an extra long contentSize and ScrollView to test everything else
[popover setContentSize:NSMakeSize([popover contentSize].width, 200)];
[scroll setFrame:(NSRect){
[scroll frame].origin.x,[scroll frame].origin.y,
[scroll frame].size.width,155
}];
NSLayoutManager* layout = [popoverTextView layoutManager];
NSTextContainer* container = [popoverTextView textContainer];
NSRect myRect = [layout usedRectForTextContainer:container]; //Broken
//Now what ?
}
I still have no idea why I can't use [layout usedRectForTextContainer:container], but I managed to get the NSTextView's height by using :
-(void)resize
{
//Get glyph range for boundingRectForGlyphRange:
NSRange range = [[myTextView layoutManager] glyphRangeForTextContainer:container];
//Finally get the height
float textViewHeight = [[myTextView layoutManager] boundingRectForGlyphRange:range
inTextContainer:container].size.height;
}
Here is some functional code that I have tested myself for swift. usedRectForTextContainer is not broken it just is set lazily. To set it i call glyphrangefortextcontainer
I found the answer here: http://stpeterandpaul.ca/tiger/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html
let textStorage = NSTextStorage(string: newValue as! String)
let textContainer = NSTextContainer(size: NSSize(width: view!.Text.frame.width, height:CGFloat.max))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = NSLineBreakMode.ByWordWrapping
textStorage.font = NSFont(name: "Times-Roman", size: 12)
layoutManager.glyphRangeForTextContainer(textContainer)
let rect = layoutManager.usedRectForTextContainer(textContainer)
Swift.print(rect)
The documentation also suggest that is the the case:

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.

CoreGraphics drawRect in a cocos2d project EXEC BAD ACCESS

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]);

Resources