Cache NSView Context - objective-c

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 šŸ˜‰

Related

Why my NSBezierPath is not showing?

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.

NSView draw permanently to background

I'm trying to draw some things on the background of my windows. Therefore I subclassed the NSView of the window and added some drawing code like this:
- (void)drawRect:(NSRect)dirtyRect {
float color = 0.95;
[[NSColor colorWithDeviceRed:color green:color blue:color alpha:1.0] set];
NSRectFill(NSMakeRect(320, 0, 220, NSHeight(dirtyRect)-60));
}
This works great, but as soon as I open a NSComboBox or if I activate a checkbox, the background of these elements erases my just drawn rect.
I don't understand this, because checking for example the checkbox causes, that drawRect is called (I added a NSLog). Only resizing the window draws my rect again.
EDIT:
here is a screenshot of the problem:
I sometimes face the same problem. I think the following is what I use.
/// .h
#interface BackgroundView1 : NSView {
NSImage *myImage;
}
// .m
- (void)awakeFromNib {
[self setupBackgroundImage];
}
- (void)setupBackgroundImage {
NSColor *c = [NSColor colorWithDeviceRed:0.0f/255.0f green:55.0f/255.0f blue:150.0f/255.0f alpha:1.0f];
if (myImage == nil)
myImage = [self createColorImage:NSMakeSize(1,1):c];
}
- (void)drawRect:(NSRect)rect {
[myImage drawInRect:NSMakeRect([self bounds].origin.x,[self bounds].origin.y,[self frame].size.width,[self frame].size.height)
fromRect:NSMakeRect(0,0,[myImage size].width, [myImage size].height)
operation:NSCompositeCopy
fraction:1.0];
}
// Start Functions //
- (NSImage *)createColorImage:(NSSize)size :(NSColor *)color {
NSImage *image = [[NSImage alloc] initWithSize:size];
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:size.width
pixelsHigh:size.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:0
bitsPerPixel:0];
[image addRepresentation:rep];
[image lockFocus]; // Lock focus of image, making it a destination for drawing
[color set];
NSRectFill(NSMakeRect(0,0,size.width,size.height));
[image unlockFocus];
return image;
}
// End Functions //

Draw image background in NSView under transparent elements

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.

Objective-C tiled background image in NSview

I have 2 NSViews arranged side by side that have backgrounds made from tiled images.
When I add constraints to the views so that they resize with the main window the tiled background image no longer displays and the background is just black.
What am I missing here?
#import "imageWellGraphics.h"
#implementation imageWellGraphics
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGAffineTransform affineTransform = CGContextGetCTM(context);
NSImage* image = [NSImage imageNamed: #"tile.png"];
NSColor* imagePattern = [NSColor colorWithPatternImage: image];
NSRect frame = NSInsetRect(self.bounds, 1, 1);
NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRect:NSMakeRect(NSMinX(frame), NSMinY(frame), NSWidth(frame), NSHeight(frame))];
[NSGraphicsContext saveGraphicsState];
CGContextSetPatternPhase(context, NSMakeSize(affineTransform.tx, affineTransform.ty));
[imagePattern setFill];
[rectanglePath fill];
[NSGraphicsContext restoreGraphicsState];
}
#end
Include this line inside drawrect method and check:-
[super drawRect:dirtyRect];
Well, it turns out the problem was me not properly understanding/using Images.xcassets for my background image.

NSView and NSScrollView: button that disappears and reappears on another position

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.