CAShapeLayer problems - not drawing in nstableview - macos

I customize a view-based tableview, and in the customize NSView, I write in the init:
NSRect testRect = imageViewRect;
CGMutablePathRef roundPath = CGPathCreateMutable();
CGPathAddArc(roundPath, NULL,
[self arcCenter:testRect].x,
[self arcCenter:testRect].y,
ArcRadius,
2 * M_PI + M_PI_2,
M_PI_2,
YES);
self.backgroundLayer = [CAShapeLayer layer];
self.backgroundLayer.frame = testRect;
self.backgroundLayer.path = roundPath;
self.backgroundLayer.strokeColor = [[NSColor blueColor] CGColor];
self.backgroundLayer.fillColor = nil;
self.backgroundLayer.lineWidth = 5.0f;
self.backgroundLayer.lineJoin = kCALineJoinBevel;
[self.layer addSublayer:self.backgroundLayer];
[self.backgroundLayer setFillColor:[NSColor yellowColor].CGColor];
but the layer is not shown, I really don't know where is the problem.
I wrote this in a view, and load the view in a window, the layer is showed correctly.

Have you turned your view 'self' a layer backed view? You will need to say
self.wantsLayer = true
before you can do any layer related manipulation.
Quoting documentation-
Setting the value of this property to true turns the view into a
layer-backed view—that is, the view uses a CALayer object to manage
its rendered content. Creating a layer-backed view implicitly causes
the entire view hierarchy under that view to become layer-backed.
Thus, the view and all of its subviews (including subviews of
subviews) become layer-backed. The default value of this property is
false.

Related

Shadow Effect not working with Resizable NSView

I have written below code for shadow effect for my NSView.
[_nsview setWantsLayer:YES];
_nsview.layer.masksToBounds = NO;
_nsview.layer.cornerRadius = 5;
_nsview.layer.shadowOffset = CGSizeMake(.3f, -.3f);
_nsview.layer.shadowRadius = 10;
_nsview.layer.shadowOpacity = 0.20;
_nsview.layer.shadowColor = [NSColor blackColor].CGColor;
_nsview is outlet of that NSView. Above code works perfectly and gives shadow effect...But problem is that after resizing _nsview shadow getting hide.
Use NSShadow instead:
[_childView setWantsLayer:YES];
_childView.layer.backgroundColor = [NSColor whiteColor].CGColor;
_childView.layer.cornerRadius = 5;
NSShadow *dropShadow = [[NSShadow alloc] init];
[dropShadow setShadowColor:[NSColor colorWithWhite:0.1 alpha:0.6]];
[dropShadow setShadowOffset:NSMakeSize(0, -5)];
[dropShadow setShadowBlurRadius:5];
_childView.shadow = dropShadow;
Unless you're using a layer-hosting view (note: different from a layer-backed view) then you should assume that NSView can change layer properties at any time behind the scenes. Shadows and transforms are the most common properties to change outside of the developer's control.
Here is some useful reading:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html
https://www.objc.io/issues/14-mac/appkit-for-uikit-developers/

How do I add a NSButton to a CALayer?

I am trying to add a NSButton on a layer inside a IKImageBrowserCell object. I found this post helpful but it doesn't get into the crux.
I've already tried this:
- (CALayer *) layerForType:(NSString*) type
{
CGColorRef color;
//retrieve some usefull rects
NSRect frame = [self frame];
NSRect imageFrame = [self imageFrame];
NSRect relativeImageFrame = NSMakeRect(imageFrame.origin.x - frame.origin.x, imageFrame.origin.y - frame.origin.y, imageFrame.size.width, imageFrame.size.height);
/* foreground layer */
if(type == IKImageBrowserCellForegroundLayer){
//no foreground layer on place holders
if([self cellState] != IKImageStateReady)
return nil;
//create a foreground layer that will contain several childs layer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
//add a checkbox to tell whether to upload this one or not
NSRect checkFrame = NSMakeRect( ( frame.size.width/2)-5 , frame.size.height - 19, 18,18);
NSButton *uploadCheckBox = [[NSButton alloc] initWithFrame:checkFrame];
[uploadCheckBox setButtonType:NSSwitchButton];
[layer addSublayer :[uploadCheckBox layer]];
return layer;
}
//(...)
return nil;
}
But unfortunately the button doesn't appear on the layer. I think the placement of the button is fine, since it's based on an example code from Apple's app. I've got a feeling that this line is wrong:
layer addSublayer :[uploadCheckBox layer]];, since I should be adding entire NSButton, not just it's bitmap representation (a layer). Any help greatly appreciated!
You cannot add a NSView inside a CALayer. You should create a new layer for your button purpose and add to your holding layer.

Capturing an offline NSView to an NSImage

I'm trying to make a custom animation for replacing an NSView with another.
For that reason I need to get an image of the NSView before it appears on the screen.
The view may contain layers and NSOpenGLView subviews, and therefore standard options like initWithFocusedViewRect and bitmapImageRepForCachingDisplayInRect do not work well in this case (they layers or OpenGL content well in my experiments).
I am looking for something like CGWindowListCreateImage, that is able to "capture" an offline NSWindow including layers and OpenGL content.
Any suggestions?
I created a category for this:
#implementation NSView (PecuniaAdditions)
/**
* Returns an offscreen view containing all visual elements of this view for printing,
* including CALayer content. Useful only for views that are layer-backed.
*/
- (NSView*)printViewForLayerBackedView;
{
NSRect bounds = self.bounds;
int bitmapBytesPerRow = 4 * bounds.size.width;
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CGContextRef context = CGBitmapContextCreate (NULL,
bounds.size.width,
bounds.size.height,
8,
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
if (context == NULL)
{
NSLog(#"getPrintViewForLayerBackedView: Failed to create context.");
return nil;
}
[[self layer] renderInContext: context];
CGImageRef img = CGBitmapContextCreateImage(context);
NSImage* image = [[NSImage alloc] initWithCGImage: img size: bounds.size];
NSImageView* canvas = [[NSImageView alloc] initWithFrame: bounds];
[canvas setImage: image];
CFRelease(img);
CFRelease(context);
return canvas;
}
#end
This code is primarily for printing NSViews which contain layered child views. Might help you too.

UIPageViewController changes size?

I'm using the Page-Based Application template in Xcode 4 to load pages of a PDF and create a UITextView over hidden text boxes so the user can write notes.
So far I have it all working, but when I add the UITextView, it's in the wrong place in landscape mode (showing 2 pages).
// ModelController.m
- (id)init
{
self = [super init];
if (self) {
NSString *pathToPdfDoc = [[NSBundle mainBundle] pathForResource:#"My PDF File" ofType:#"pdf"];
NSURL *pdfUrl = [NSURL fileURLWithPath:pathToPdfDoc];
self.pageData = CGPDFDocumentCreateWithURL((__bridge CFURLRef)pdfUrl); // pageData holds the PDF file
}
return self;
}
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{
// Return the data view controller for the given index.
if( CGPDFDocumentGetNumberOfPages( self.pageData ) == 0 || (index >= CGPDFDocumentGetNumberOfPages( self.pageData )))
return nil;
// Create a new view controller and pass suitable data.
DataViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:#"DataViewController"];
dataViewController.dataObject = CGPDFDocumentGetPage( self.pageData, index + 1 ); // dataObject holds the page of the PDF file
[dataViewController view]; // make sure the view is loaded so that all subviews can be accessed
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake( 10, 20, 30, 40 )];
textView.layer.borderWidth = 1.0f;
textView.layer.borderColor = [[UIColor grayColor] CGColor];
[dataViewController.dataView addSubview:textView]; // dataView is a subview of dataViewController.view in the storyboard/xib
CGRect viewFrame = dataViewController.dataView.frame; // <- *** THIS IS THE WRONG SIZE IN LANDSCAPE ***
}
This behavior really surprised me, because viewControllerAtIndex isn't called when I rotate the iPad, so I have no way of knowing what the real size of the view frame is. I get the same view frame in both portrait and landscape:
# in Xcode console:
po [dataViewController view]
# result in either orientation:
(id) $4 = 0x0015d160 <UIView: 0x15d160; frame = (0 20; 768 1004); autoresize = RM+BM; layer = <CALayer: 0x15d190>>
#
Does anyone know if there is a transform I'm supposed to use to position the UITextView correctly? I'm concerned that I may have to store the locations of the elements independently and reposition them upon receiving shouldAutorotateToInterfaceOrientation messages.
It seems that Apple may have implemented UIPageViewController improperly, but all I could find was this partial workaround that I'm still trying to figure out:
UIPageViewController and off screen orientation changes
Thanks!
I think the trick here is to override viewDidLayoutSubviews in your DataViewController and manage the size of all your programmatically-inserted non-autosizing views, since you don't really know what the parent is going to do to its subviews until that time.
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.textView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}

How to shadow documentView in NSScrollView?

How to shadow documentView in NSScrollView?
The effect look likes iBook Author:
You need to inset the content in your document view to allow space for the shadow to be displayed, then layer back the view and set a shadow on it. Example:
view.wantsLayer = YES;
NSShadow *shadow = [NSShadow new];
shadow.shadowColor = [NSColor blackColor]
shadow.shadowBlurRadius = 4.f;
shadow.shadowOffset = NSMakeSize(0.f, -5.f);
view.shadow = shadow;
The NSScrollView contentView is an NSView subclass, which has a shadow field, if you create a shadow object and assign it to this field, the view will automatically show a drop shadow when drawn
NSShadow* shadow = [[NSShadow alloc] init];
shadow.shadowBlurRadius = 2; //set how many pixels the shadow has
shadow.shadowOffset = NSMakeSize(2, -2); //the distance from the view the shadow is dropped
shadow.shadowColor = [NSColor blackColor];
self.scrollView.contentView.shadow = shadow;
This works because all views when are drawn on drawRect use this shadow property by using [shadow set].
doing [shadow set] during a draw operation makes whatever is drawn after that to be replicated underneath
I'm new to entering posts on stack overflow but I had the same issue and have solved it so I thought after searching the net for hours to find a solution it would be nice to answer it.
My solution is to create a subclass for NSClipView with the following code for drawRect...
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
NSRect childRect = [[self documentView] frame];
[NSGraphicsContext saveGraphicsState];
// Create the shadow below and to the right of the shape.
NSShadow* theShadow = [[NSShadow alloc] init];
[theShadow setShadowOffset:NSMakeSize(4.0, -4.0)];
[theShadow setShadowBlurRadius:3.0];
// Use a partially transparent color for shapes that overlap.
[theShadow setShadowColor:[[NSColor grayColor]
colorWithAlphaComponent:0.95f]];
[theShadow set];
[[self backgroundColor] setFill];
NSRectFill(childRect);
// Draw your custom content here. Anything you draw
// automatically has the shadow effect applied to it.
[NSGraphicsContext restoreGraphicsState];
}
You then need to create an instance of the subclass and set it with the setContentView selector.
You also need to repaint the clip view every time the content view size changes. If you have your content view set up to change in terms of canvas size when the user wants then unless you repaint the clip view some nasty shadow marks will left behind.
You don't need to mess about with clips as others have suggested.
Hope it helps!

Resources