Rounded corners for NSView subclass not working - cocoa

I've got a custom NSView subclass which I want to have rounded corners. I use the following code in the .m file:
#import "ItemImageSelectionView.h"
#implementation ItemImageSelectionView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.wantsLayer = YES;
self.layer.frame = self.frame;
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor colorWithCalibratedRed:0.0 green:0.5 blue:1 alpha:1] set];
NSRectFill(dirtyRect);
[self.layer setCornerRadius:5.0];
}
#end
I use this code to initialize the view, pretty default:
NSView *imageSelectionView = [[ItemImageSelectionView alloc] initWithFrame:CGRectMake(imageView.frame.origin.x - 2, imageView.frame.origin.y - 2, imageView.frame.size.width + 4, imageView.frame.size.height + 4)];
[self addSubview:imageSelectionView positioned:NSWindowBelow relativeTo:imageView];
But it doesn't set any rounded corners! What am I doing wrong?

You don't need to set rounded corners in the drawrect method; set that in the init. Also, you should probably call the super method, [super drawRect:dirtyRect] in your overrided method. I'm not sure how your fill custom will interact with the layers, though. You may need to set [self.layer setMasksToBounds:YES];

Related

Custom NSButtonCell, drawBezelWithFrame not called

I'm trying to figure out how to custom draw Buttons in Cocoa/OSX. Since my view is custom drawn I will not use IB and want to do it all in code. I created a subclass of NSButtonCell and a subclass of NSButton. In the Subclass of NSButtonCell I override the Method drawBezelWithFrame:inView: and in the initWithFrame Method of my subclassed NSButton I use setCell to set my CustomCell in the Button. However, drawBezelWithFrame gets not called and I don't understand why. Can someone point out what I've done wrong or what I miss here?
Subclass of NSButtonCell:
#import "TWIButtonCell.h"
#implementation TWIButtonCell
-(void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
{
//// General Declarations
[[NSGraphicsContext currentContext] saveGraphicsState];
//// Color Declarations
NSColor* fillColor = [NSColor colorWithCalibratedRed: 0 green: 0.59 blue: 0.886 alpha: 1];
//// Rectangle Drawing
NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRect: NSMakeRect(8.5, 7.5, 85, 25)];
[fillColor setFill];
[rectanglePath fill];
[NSGraphicsContext restoreGraphicsState];
}
#end
Subclass of NSButton:
#import "TWIButton.h"
#import "TWIButtonCell.h"
#implementation TWIButton
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
TWIButtonCell *cell = [[TWIButtonCell alloc]init];
[self setCell:cell];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
#end
Usage:
- (void)addSendButton:(NSRect)btnSendRectRect
{
TWIButton *sendButton = [[TWIButton alloc] initWithFrame:btnSendRectRect];
[self addSubview:sendButton];
[sendButton setTitle:#"Send"];
[sendButton setTarget:self];
[sendButton setAction:#selector(send:)];
}
Following are the things seems to be missing out from your code.
You are not calling the [super drawRect:dirtyRect]
You are not overriding + (Class)cellClass in the Class(TWIButton) which is derived from NSButton.
Below is the code after changes:
#implementation TWIButton
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
TWIButtonCell *cell = [[TWIButtonCell alloc]init];
[self setCell:cell];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
//Changes Added!!!
[super drawRect:dirtyRect];
}
//Changes Added!!!!
+ (Class)cellClass
{
return [TWIButtonCell class];
}
#end
Now keep the break point at drawBezelWithFrame and check it will get called.
One might forgo the NSButton subclass, since it looks like you are ONLY using it to instantiate the Cell type within the initializer.
Simply
NSButton *button ...
[button setCell: [[TWIButtonCell alloc] init] autorelease]];
btw. you will probably have a leak in the previous examples since you init and then call setCell that likely has its own retain.

Equivalent of clipToBounds for Mac (NSView's layer)

I have a class "GlobalView" which extends NSView and has two subviews GreenRectView (which extends NSView).
I use the CALayer from my GreenRectView in order to be able to rotate (with setAffineTransform:) my view around a point other than the default bottom-left point. Thus I used "setAnchorPoint". Everything works well except that when my green rect is rotated , it is not draw outside the bounds of its view. In iOS it is possible to use clipToBounds:NO to accept that drawing is display outside bounds but in Mac it doesn't work with masksToBounds... How can I rotate my greenrect and display it entirely whatever the angle of rotation.
See picture :
And here is the code :
#implementation GlobalView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
g = [[GreenRectView alloc]initWithPoint:NSMakePoint(200, 200)];
[self addSubview:g];
g = [[GreenRectView alloc]initWithPoint:NSMakePoint(100, 400)];
[self addSubview:g];
}
return self;
}
#import <Quartz/Quartz.h>
#include <ApplicationServices/ApplicationServices.h>
#implementation GreenRectView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setWantsLayer:YES];
layer = [self layer];
[self setLayer:layer];
[layer setAnchorPoint:NSMakePoint(0.0,0.5)];
[layer setMasksToBounds:NO];
[menuLayer addSublayer:layer];
}
return self;
}
- (id)initWithPoint:(CGPoint)p {
return [self initWithFrame:NSMakeRect(p.x, p.y, 120, 100)];
}
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor greenColor] setFill];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(0,0, self.frame.size.width, self.frame.size.height) xRadius:5.5 yRadius:5.5];
[path stroke];
}

adding subviews to an NSView to have a chess-like grid

I am trying to create a Cocoa UI that consists of two sets of squares (chess-like grids) that will assume different colours while an underlying algorithm is running. When the execution of the algorithm comes to an end, the UI should be able to handle clicks, panning and other gestures.
The hierarchy I have so far is the following (please check the attached code for specifics):
1) the main window that is the window of a window controller
2) a split view with two custom views, mainView and sideView (each one would hold a set of squares)
3) two view controllers (mainViewController and sideViewController)
I would like to be able to load the squares as subviews of mainView and sideView.
I thought of having another custom view, say SquareView with another nib file. My questions would be:
a) how do I create this SquareView so that it can be used to create the squares that will be added to mainView and sideView as subviews to form chess-like grids?
b) how do I add subviews to mainView and sideView to built the two grids? For the sake of simplicity, let's assume there would be four non-overlapping squares for each of the previously mentioned views.
Thank you!
MainView.m
#import "MainView.h"
#implementation MainView
- (void)drawRect:(NSRect)TheRect
{
[[NSColor grayColor] set];
[NSBezierPath fillRect:[self bounds]];
}
SideView.m
#import "SideView.h"
#implementation MainView
- (void)drawRect:(NSRect)TheRect
{
[[NSColor whiteColor] set];
[NSBezierPath fillRect:[self bounds]];
}
MainWindowController.h
#import <Cocoa/Cocoa.h>
#class SideViewController;
#class MainViewController;
#interface MainWindowController : NSWindowController
{
IBOutlet NSSplitView* oMainSplitView;
SideViewController* sideViewController;
MainViewController* mainViewController;
}
#end
MainWindowController.m
#import "MainWindowController.h"
#import "SideViewController.h"
#import "MainViewController.h"
#implementation MainWindowController
- (void)windowDidLoad
{
sideViewController = [[SideViewController alloc] initWithNibName:#"SideView" bundle:nil];
NSView* splitViewLeftView = [[oMainSplitView subviews] objectAtIndex:0];
NSView* sideView = [sideViewController view];
[sideView setFrame:[splitViewLeftView bounds]];
[sideView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[splitViewLeftView addSubview:sideView];
mainViewController = [[MainViewController alloc] initWithNibName:#"MainView" bundle:nil];
NSView* splitViewRightView = [[oMainSplitView subviews] objectAtIndex:1];
NSView* mainView = [mainViewController view];
[mainView setFrame:[splitViewRightView bounds]];
[mainView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[splitViewRightView addSubview:mainView];
}
You can make this as simple or as complicated as you desire: simple? do everything you want in MainView's drawRect method; complex: nest NSViews (or NSCell's, or NSBox's, etc.) and have each one draw itself.
Personally, I'd vote to keep it simpleā€¦
a) I think the easiest way would be to create a matrix of NSBoxes, which you could do in code or in IB. Having the squares in a matrix would make it easy to loop through them or access specific ones.
b) I'm not sure what your question is here -- you would do it just as you did in your posted code, using [mainView addSubview:squareMatrix];
After Edit: Actually, it looks like IB won't let you embed NSBoxes in a matrix. In the past, I've made a matrix of subclassed NSButtonCells (to allow background color with no border) that had a grid of 64x64 cells that were clickable and would change color with those clicks. I don't know if you want a fixed number of cells in your views, or do you need to dynamically change the number? Something like this could work for you I think -- I actually created this in code, because IB was really slow in updating with that many cells.
Here is what I did. In my case, I needed cells with no border but with background color, so I had to subclass NSButtonCell, like this:
-(id)initWithRGBAlpha:(NSArray *)rgbAlpha {
if (self == [super init]) {
NSColor *color = [NSColor colorWithCalibratedRed:[[rgbAlpha objectAtIndex:0]doubleValue]
green:[[rgbAlpha objectAtIndex:1]doubleValue]
blue:[[rgbAlpha objectAtIndex:2]doubleValue]
alpha:[[rgbAlpha objectAtIndex:3]doubleValue]];
[self setBackgroundColor:color];
[self setTitle:#""];
[self setBordered:NO];
[self setTag:0];
[self setImageScaling:3];
return self;
}else{
return nil;
}
}
-(void) setState:(NSInteger)value {
if (value == 1) {
self.backgroundColor = self.selectedColor;
[super setState:value];
}else {
self.backgroundColor = self.backgroundColor;
[super setState:value];
}
}
-(void) setBackgroundColor:(NSColor *)color {
backgroundColor = color;
selectedColor = [color colorWithAlphaComponent:.75];
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:self.backgroundColor forKey:#"bColor"];
}
- (id)initWithCoder:(NSCoder *)decoder {
[super initWithCoder:decoder];
self.backgroundColor = [decoder decodeObjectForKey:#"bColor"];
return self;
}
I created the matrix in code, like so:
#implementation RDMatrix
-(void) initWithParentView:(NSView *) cv {
NSNumber *one = [NSNumber numberWithInt:1];
NSArray *colors = [NSArray arrayWithObjects:one,one,one,one,nil];
RDButtonCell *theCell = [[RDButtonCell alloc ]initWithRGBAlpha:colors];
[self initWithFrame:NSMakeRect(200,100,1,1) mode:2 prototype:theCell numberOfRows:64 numberOfColumns:64];
[self setSelectionByRect:TRUE];
[self setCellSize:NSMakeSize(8,8)];
[self sizeToCells];
self.target = self;
self.action = #selector(matrixClick:);
self.backgroundColor = [NSColor lightGrayColor];
self.drawsBackground = TRUE;
self.autoresizingMask = 8;
self.allowsEmptySelection = TRUE;
[cv addSubview:self];
}
-(void) matrixClick: (id) sender {
for (RDButtonCell *aCell in self.selectedCells){
if ([self.selectedCells count] < 64) {
aCell.backgroundColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1];
}else{
aCell.backgroundColor = [NSColor colorWithCalibratedRed:0 green:.5 blue:1 alpha:1];
}
}
[self deselectAllCells];
}
#end

setNeedsDisplay for DrawRect after button press

I have this in a custom class on my NSView and when I press a button using my drawMyRec IBAction I want to change the color of my NSRect however this is not working, can anyone help?
#import "myView.h"
#implementation myView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
RectColor = [[NSColor blackColor] init];
}
return self;
}
- (IBAction)drawMyRec:(id)sender;
{
NSLog(#"pressed");
RectColor = [[NSColor blueColor] init];
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRectFill(dirtyRect);
[RectColor set];
}
#end
Your drawRect function is incorrect. change to this:
- (void)drawRect:(NSRect)dirtyRect
{
[RectColor set];
NSRectFill(dirtyRect);
}
First, why are you calling -init on an NSColor? Second, you're setting the color after you've drawn the rect, so it won't take affect until the next redraw. 3rd, what's dirtyRect when -drawRect: is called? Why not just fill the entire rect regardless of what's dirty?

Cocoa -- getting a simple NSImageView to work

I am confused about why this code does not display any image:
In the app delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSRect rect = window.frame;
rect.origin.x = 0;
rect.origin.y = 0;
BlueImageView *blueImageView = [[BlueImageView alloc]initWithFrame:rect];
window.contentView = blueImageView; // also tried [window.contentView addSubview: blueImageView];
}
BlueImageView.h:
#interface BlueImageView : NSImageView {
}
#end
BlueImageView.m:
#implementation BlueImageView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setImage: [NSImage imageNamed:#"imagefile.png"]];
NSAssert(self.image, #"");
NSLog (#"Initialized");
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
}
#end
The file imagefile.png exists. The NSAssert is not causing an exception. The NSLog is firing. But no image shows up in the window.
The drawRect: method is called to draw the view, and your implementation immediately returns. To get NSImageView to draw the image for you, call [super drawRect:dirtyRect]; in your implementation of drawRect:. If you aren't going to do any other drawing in drawRect:, just remove the method to speed up drawing.

Resources