I am at the 17th chapter of Aaron's Cocoa programming for Mac os X, and in the example he embeds a NSView in a NSScrollView.
For exercise I also have added a NSButton programmatically to the view.
The problem is a strange behaviour of the button, which firstly appears on the scroll view, but when I move down the vertical scroller, the button disappears and reappears in the bottom of the scroll view.Since this may be confusing (and also hard to explain), I have made a video to better describe the problem:
http://tinypic.com/player.php?v=k1sacz&s=6
I have subclassed NSView and called the class StretchView (as the book says).
This is the code:
#import <Cocoa/Cocoa.h>
#interface StretchView : NSView
{
#private
NSBezierPath* path;
}
- (NSPoint) randomPoint;
- (IBAction) click : (id) sender;
#end
#import "StretchView.h"
#implementation StretchView
- (void) awakeFromNib
{
// Here I add the button
NSView* view=self;
NSButton* button=[[NSButton alloc] initWithFrame: NSMakeRect(10, 10, 200, 100)];
[button setTitle: #"Click me"];
[button setTarget: self];
[button setAction: #selector(click:)];
[view addSubview: button];
}
- (IBAction) click:(id)sender
{
NSLog(#"Button clicked");
}
- (void) drawRect:(NSRect)dirtyRect
{
NSRect bounds=[self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect: bounds];
[[NSColor whiteColor] set];
[path fill];
}
- (id) initWithFrame:(NSRect)frameRect
{
self=[super initWithFrame: frameRect];
if(self)
{
// here i dra some random curves to the view
NSPoint p1,p2;
srandom((unsigned int)time(NULL));
path=[NSBezierPath bezierPath];
[path setLineWidth: 3.0];
p1=[self randomPoint];
[path moveToPoint: p1];
for(int i=0; i<15; i++)
{
p1=[self randomPoint];
p2=[self randomPoint];
[path curveToPoint: [path currentPoint] controlPoint1: p1 controlPoint2: p2 ];
[path moveToPoint: p1];
}
[path closePath];
}
return self;
}
- (NSPoint) randomPoint
{
NSPoint result;
NSRect r=[self bounds];
result.x=r.origin.x+random()%(int)r.size.width;
result.y=r.origin.y+random()%(int)r.size.height;
return result;
}
#end
Questions:
1) Why does the button disappear - reappear and how to avoid this problem?
2) Why are the curves filled in white? I wanted to draw them as tiny lines, not filled.
Part 1:
What it looks like is that the scroll view isn't updating its scroll bars to where the view is actually scrolled. (Upon launch, it seems like it's showing that the view is scrolled down to the bottom left already, even when the scroll bar is at the top).
I can only ask, for now, what OS are you running? It works perfectly for me on Mountain Lion, unless I didn't duplicate your code correctly.
Part 2:
The paths are filled because you used [path fill] in drawRect. Use [path stroke] instead, for strokes.
Related
So i recieve a notification with the data i need to draw my path. This code is from my main app controller:
-(void) handleAdd:(NSNotification *)aNotification{
NSLog(#"x1:%f y1:%f x2:%f y2:%f ",[panelController x1],[panelController y1],[panelController x2],[panelController y2]);
myPath = [[NSBezierPath alloc]init];
[myPath setLineWidth:[panelController grosor]];
[myPath moveToPoint:NSMakePoint([panelController x1],[panelController y1])];
[myPath lineToPoint:NSMakePoint([panelController x2],[panelController y2])];
[[panelController trazado] setStroke];
[myPath stroke];
}
The NSLog is showing me the right data. I have a view created and in that white rectangle is where i want to draw.
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRect bounds= [self bounds];
[[NSColor whiteColor] set];
[NSBezierPath fillRect:bounds];
}
If i draw in that class the stroke is showing, but how could i draw on that view from the controller? Or should i just receive the notification in the view and draw from that class?
You can't (easily) draw from the controller into the view.
Think of your views drawRect method as a complete set of instructions of what to draw at every refresh.
So right now all you are saying is "fill me with white"
You need to handle all the relevant drawing within the views drawRect: method.
Adjust your controller method to this.
-(void) handleAdd:(NSNotification *)aNotification{
[myViewInstance setNeedsDisplay:YES]; //also myViewInstance.needsDisplay = YES
}
And assuming the view has a reference to the panel controller…
#interface MyView: UIView
//weak as you don't want a reference cycle
#property (weak) MyPanelController *panelController;
#end
#implementation MyView
- (void)drawRect:(NSRect)dirtyRect
{
NSRect bounds= [self bounds];
[[NSColor whiteColor] set];
[NSBezierPath fillRect:bounds];
NSBezierPath *myPath = [[NSBezierPath alloc]init];
[myPath setLineWidth:[self.panelController grosor]];
[myPath moveToPoint:NSMakePoint([self.panelController x1],[self.panelController y1])];
[myPath lineToPoint:NSMakePoint([self.panelController x2],[self.panelController y2])];
[[self.panelController trazado] setStroke];
[myPath stroke];
}
#end
If you want explore your first technique you want to look at at NSView and the lockFocus/unlockFocus methods. The documentation explains why you probably don't want to do this.
I want to draw image background in NSView and place some elements which also have transparent background on top of it.
I've overrided draw rect of NSView:
- (void)drawRect:(NSRect)rect
{
[NSGraphicsContext saveGraphicsState];
NSImage *image = [NSImage imageNamed:#"header_background.gif"];
[image drawInRect:CGRectMake(0, rect.size.height - 75, rect.size.width, 75) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
}
NSWindow init:
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
self = [super initWithContentRect:contentRect styleMask:aStyle backing:bufferingType defer:flag];
if (self) {
[self setOpaque:NO];
[self setHasShadow:YES];
[self setBackgroundColor:[NSColor clearColor]];
}
return self;
}
After that a've got such weird effect. Backgrounds of Sliders and buttons are also blinking sometimes
What am I doing wrong?
Well, first off, your “color” is ignored when drawing images, so that’s not doing anything. Second, the code I see here probably isn’t causing the problem, can you post the rest of the file?
Right now I’m suspecting maybe you have marked this view as being opaque, when it’s not, or that you have something else odd in your view hierarchy.
I'm trying to store NSView Current Context and draw it later to NSView again. I was wondering what is the fastest and most efficient way to do this?
You can draw your content to an NSImage and just redraw the image later, but you need to invalidate the cache when needed (this depends on what your view does).
Example:
#interface SOCacheView : NSView
#end
#implementation SOCacheView
{
NSImage *_cache;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
if (!_cache) [self prepareImage];
[_cache drawAtPoint:NSZeroPoint fromRect:self.bounds operation:NSCompositeSourceOver fraction:1.0];
}
- (void)prepareImage
{
_cache = [[NSImage alloc] initWithSize:self.bounds.size];
[_cache lockFocus];
// do the drawing here...
[[NSColor blueColor] setFill];
NSRectFill(NSMakeRect(0, 0, NSWidth(self.bounds)/2, NSHeight(self.bounds)));
[[NSColor redColor] setFill];
NSRectFill(NSMakeRect(NSWidth(self.bounds)/2, 0, NSWidth(self.bounds)/2, NSHeight(self.bounds)));
[_cache unlockFocus];
}
To invalidate the cached drawing, just set _cache to nil.
Here's a sample project for you to play with 😉
I have a regular DocumentView class in a Window. I have the following code once a user presses a button:
- (void)handleButtonPress:(NSNotification *)note{
// draw new graph view
EDGraphView *graph = [[EDGraphView alloc] init];
[self addSubview:graph];
[self setNeedsDisplay:TRUE];
NSLog(#"Button was pressed");
}
This gets called correctly because I get the output "Button was pressed" every time I click on the button. In addition to that the drawRect method below of the view gets called as well.
- (void)drawRect:(NSRect)dirtyRect
{
NSRect bounds = [self bounds];
[[NSColor whiteColor] set];
[NSBezierPath fillRect:bounds];
for(EDGraphView *graph in [self subviews]){
[graph setNeedsDisplay:TRUE];
NSLog("calling set needs display on graph object!");
}
}
However when I go in the EDGraphView class and edit the drawRect method to look like the following
- (void)drawRect:(NSRect)dirtyRect
{
NSLog(#"redrawing graph view.");
}
It never gets called! I must be missing something about the whole setNeedsDisplay and drawRect process.
Any suggestions?
hi Normaly you don't call setneeddisplay into a drawrect.
have you tried (with the super)? :
- (void)drawRect:(NSRect)dirtyRect
{
NSRect bounds = [self bounds];
[[NSColor whiteColor] set];
[NSBezierPath fillRect:bounds];
[super drawRect:rect];
}
Got it...I needed to make the following init call on my subview:
EDGraphView *graph = [[EDGraphView alloc] initWithFrame:bounds];
Now it calls the drawRect method!
I have a subclass of NSWindow to customize one of my windows for my app.
I have everything set, but I am not sure how to make the corners round.
Currently, my window is a transparent rectangular window with some buttons, labels, and a textfield in it.
The class includes:
#import "TransparentRoundRectWindow.h"
#implementation TransparentRoundRectWindow
-(id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self) {
[self setAlphaValue:0.75];
[self setOpaque:YES];
[self setHasShadow:YES];
[self setBackgroundColor:[NSColor clearColor]];
}
return self;
}
-(BOOL)canBecomeKeyWindow
{
return YES;
}
I just need to make the corners round now. I tried searching for similar situations and saw some of them explaining to override the drawRect method but I couldn't get them to work.
How could I do this?
(I'm using Mac OS X Lion)
Thanks in advance.
You need to set to Your window Opaque to NO. And subclass Your window's view.
Window subclass:
-(id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self) {
[self setOpaque:NO];
[self setHasShadow:YES];
[self setBackgroundColor:[NSColor clearColor]];
}
return self;
}
-(BOOL)canBecomeKeyWindow
{
return YES;
}
Window's view subclass:
- (void)drawRect:(NSRect)rect
{
NSBezierPath * path;
path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:8 yRadius:8];
[[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.75] set];
[path fill];
}
Result:
More explanation how to do this:
Create new NSView class and paste "Window's view subclass" code which I wrote in it. Then go to Your window's view.
Here is window's view click on it:
Go to the Identity inspector and set class to your created class: