CALayer renderInContext: Position of Drawing - core-animation

I have a UIView with a custom shape drawn in drawRect:. The frame property is set to:
{5.f, 6.f, 50.f, 50.f}
Now, I render the view in an Image Context, but it is ignoring the frame property of the UIView, and always drawing the UIView in the top left.
[_shapeView.layer renderInContext:UIGraphicsGetCurrentContext()];
I tried to change the frame of the CALayer, but nothing changed. Modifying the bounds property made things worse. The only work around I found useful was:
CGRect frame = _shapeView.frame;
CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
[_shapeView.layer renderInContext:context];
But, this is impractical when I am dealing with many shapes, I want to just use the frame property.

Using CGContextTranslateCTM is the proper way to go. As renderInContext: documentation states : "Renders in the coordinate space of the layer." This means the frame origin of your layer/view is ignored.
Regarding CGContextDrawLayer, it is not made to be used with CALayer, but with CGLayer. These are two very different things, and explains your crash.

Related

How to customize a NSSlider

I'm trying to implement a custom slider in Cocoa with 5 values. See my demo project, which can be downloaded here: http://s000.tinyupload.com/index.php?file_id=07311576247413689572.
I've subclassed the NSSliderCell and implemented methods like drawKnob:(NSRect)knobRect and drawBarInside:(NSRect)cellFrame flipped:(BOOL)flipped etc.
I'm facing some issues:
I'm not able to position the knob correctly regarding to the background image. I know that I'm able to change the knob's frame, and I've tried doing some calculation to position the knob correctly, but I'm not able to make it work for my custom slider. Could someone please help me with this?
The height of my custom slider background is 41px. In the drawBarInside:(NSRect)cellFrame flipped:(BOOL)flipped I change the height of the frame to 41px as well, but the entire background is not visible. Why?
I've noticed that the included images (the background and knob) are flipped vertically. Why? Note that the border top is darker in the background compared to the bottom, but this is reversed when I draw the background.
I found a mistake in your calculation of the x position of the knob rectangle: You used the height of the image where you should have used the width.
The cell drawing is being clipped to the frame of the control. Maybe you could expand the control frame when your cell awakes.
You need to use the NSImage method drawInRect:fromRect:operation:fraction:respectFlipped:hints:, and pass YES for the respectFlipped: parameter. Apple's controls generally do use flipped coordinates.
Added: Expanding the frame in awakeFromNib doesn't seem to work, the frame gets set back. Here's something that does work. Instead of overriding drawBarInside:flipped:, add this override:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSRect controlFrame = [controlView frame];
float bgHeight = self.backgroundImage.size.height;
if (controlFrame.size.height < bgHeight)
{
controlFrame.size.height = bgHeight;
[controlView setFrame: controlFrame];
}
[self.backgroundImage
drawInRect: [controlView bounds]
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0
respectFlipped: YES
hints: NULL];
[self drawKnob];
}

Drawing shadow around NSImageView

I'm trying to draw shadow under an NSImageView. I'm accessing the CALayer of the view and setting its shadow properties there:
[self.originalImageView setWantsLayer:YES];
CALayer *imageLayer = self.originalImageView.layer;
[imageLayer setShadowRadius:5.f];
[imageLayer setShadowOffset:CGSizeZero];
[imageLayer setShadowOpacity:0.5f];
[imageLayer setShadowColor:CGColorCreateGenericGray(0, 1)];
imageLayer.masksToBounds = NO;
But even though I have set masks to bounds as NO, I'm getting this:
Look carefully at the shadow. It is displayed perfectly horizontally, but vertically, it's clipped. The image is aspect-fitted, and both the image and the image view can be arbitrary size. If I try it with NSShadow instead of layer's shadow, I'm getting exactly the same results. I could use myView.clipsToBounds = NO in iOS and it used to solve this problem, however I can't find that property on Mac.
How can I draw shadow under arbitrary-sized NSImageView without clipping?
With the latest Xcode, I've discovered that I can apply CI filters to views (I don't know if that feature was also available before or not.) and I went and applied a shadow on my image view on the container view's layer. It works perfectly. Bottomline: If I apply the filter on image view's own layer, the same thing happens, but if I apply it on the container view's layer, it works!

How to create a "stretchable" UIView

I have a UIView that contains another UIView. The outer UIView draws a border around the inner UIView via drawRect. (The border is too complicated to be drawn via CALayer properties.)
At present, when I animate the resizing of the outer UIView, its drawRect method is called once at the beginning of the animation and the result is stretched or shrunk. This does not look good.
I am looking for a way to either redraw the content at every step of the animation, or find a way to achieve the same visual effect. (The result should be similar to the resizing of a stretchable UIImage.)
You should change view's content type to:
your_view.contentMode = UIViewContentModeRedraw;
And it will redraw each time its frame changes.
I ended up adding subviews with autoresizing masks that kept them positioned correctly during the animation.
You need to send a [UIView setNeedsToDisplay] to the view for every time the frame size is changed, you could try overriding the setFrame: method like
- (void)setFrame:(CGRect)r
{
[super setFrame:r];
[self setNeedsToDisplay];
}

Blurred background underneath an NSView

I've been looking around but I can't find an answer on this. I'm trying to make a view that will display it's contents normally, but anything that's underneath it (in the z axis) would be blurred.
I can't seem to manage to do this right off the bat. Anything I try (at best) blurs the contents of the view, not what's underneath.
Check out this article on Cocoanetics, it gives you some instructions how to properly set up a NSView with blurred background: http://www.cocoanetics.com/2013/10/blurring-views-on-mac/
There's also a RMBlurredView class on Github: https://github.com/raffael/RMBlurredView
The idea behind it is to use a layer-backed NSView with a CIGaussianBlur CIFilter applied on the backgroundFilters property. All important flags are set correctly in the mentioned class.
If you use an image that is significantly smaller than the view's frame and scale the image up, it will be blurred.
You could use CALayers. Declare a root layer and add the background image as a sublayer. Put the "contents" in a subview with a transparent background. (Otherwise the "contents" would probably be obscured by the blurred sublayer.)
Here's partial (and untested) code for setting up the sublayer:
// Set up the root layer.
[[self.aViewController view] setLayer:[CALayer layer]];
[[self.aViewController view] setWantsLayer:YES];
// Set up a sublayer.
CALayer *blurredSublayer = [CALayer layer];
NSRect rectOfView = [self.aViewController view] frame];
blurredSublayer.bounds = rectOfView;
// Views are usually positioned from their lower left corner, but CALayers are positioned from their center point.
blurredSublayer.position = CGPointMake(rectOfView.size.width/2, rectOfView.size.height/2);
// Set the sublayer's contents property to the image you've chosen. You might need to do the scaling when you set up the CGImageRef used by the contents property. (I haven't done this; I leave it to you.)
// Add the sublayer to the view's root layer.
[self.aViewController.view.layer addSublayer:blurredSublayer];
// You'll probably want to save expense by calling setWantsLayer:NO for the contents-bearing subview, since it will have been turned on when you set up the superview's root layer.
Or it might be easier to use a CGContext. But with the CALayer you have the the zPosition property you mentioned.
P.S. The use of "contents" and "superview" and "sublayer" make the structure confusing. Here's a descriptive hierarchy. The first-named item is on the bottom and the last-named on top, as it would be in IB:
superview with root layer
superview's blurred sublayer (sublayer's contents property is scaled-up image)
subview/s (textfields or whatever you had in mind as "contents")

How do you position a larger NSImage inside of a smaller NSImageView programmatically?

Let's say I have an NSImage that's 100x100. I also have an NSImageView that's 50x50. Is there a way I can place the NSImage at coordinates inside the NSImageView, so I can control which part of it shows? It didn't seem like NSImage had an initWithFrame method...
I did this in my NSImageView subclass, as Andrew suggested.
- (void)drawRect:(NSRect)rect
{
[super drawRect:rect];
NSRect cropRect = NSMakeRect(x, y, w, h);
[image drawAtPoint:NSZeroPoint
fromRect:cropRect
operation:NSCompositeCopy
fraction:1];
}
I don't believe so, but it's trivial to roll your own NSImageView equivalent that supports center/stretch options by drawing the image yourself.
Make your imageview as big as the image, and put it inside a scrollview. Hide the scrollers if you want. No need for subclassing in this case.
NSImageView has a method -setImageAlignment: which lets you control how the image is aligned within the image view. Unfortunately, if you want to display part of the image that doesn't correspond to any of the NSImageAlignment values, you're going to have to draw the image programmatically.
Depends on what your eventual goal is but the easiest thing to me seems to put your NSImageView inside an NSView (or a subclass – doesn't have to be NSScrollView as "#NSResponder" user suggests but this should work well too), set its imageScaling to NSImageScaleProportionallyUpOrDown and its frameSize to image's size. Then you can move your NSImageView freely around the upper view using setFrame:myDesiredFrame. No subclassing, no manual redrawing, etc.