create multiple drawn lines - objective-c

so i need to draw 2 different lines. through another posting, i figured out how to redraw a line. my question is, if i want to draw 2 lines do i need to have 2 code segments to do it? or is there a way to draw n lines?
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
NSLog(#"drawRect called");
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextMoveToPoint(context, self.startPoint.x, self.startPoint.y);
CGContextAddLineToPoint(context, self.endPoint.x, self.endPoint.y);
CGContextStrokePath(context);
}
implementation
draw2D *myCustomView = [[draw2D alloc] init];
myCustomView.startPoint = CGPointMake(0, 0);
myCustomView.endPoint = CGPointMake(300, 300);
myCustomView.lineWidth = 5;
myCustomView.lineColor = [UIColor redColor];
myCustomView.frame = CGRectMake(0, 0, 500, 500);
[myCustomView setBackgroundColor:[UIColor blueColor]];
[self.view addSubview:myCustomView];
[myCustomView setNeedsDisplay];
myCustomView.endPoint = CGPointMake(100, 100);
myCustomView.lineColor = [UIColor orangeColor];
[myCustomView setNeedsDisplay];
this will just redraw the line. do i repeat the drawRect block for each line i want? so if i want two lines, do i have to do:
- (void)drawRect:(CGRect)rect
{
//line1
CGContextRef context1 = UIGraphicsGetCurrentContext();
NSLog(#"drawRect called");
CGContextSetLineWidth(context1, self.lineWidth1);
CGContextSetStrokeColorWithColor(context1, self.lineColor1.CGColor);
CGContextMoveToPoint(context1, self.startPoint1.x, self.startPoint1.y);
CGContextAddLineToPoint(context1, self.endPoint1.x, self.endPoint1.y);
CGContextStrokePath(context1);
//line 2
CGContextRef context2 = UIGraphicsGetCurrentContext();
NSLog(#"drawRect called");
CGContextSetLineWidth(context2, self.lineWidth2);
CGContextSetStrokeColorWithColor(context2, self.lineColor2.CGColor);
CGContextMoveToPoint(context2, self.startPoint2.x, self.startPoint2.y);
CGContextAddLineToPoint(context2, self.endPoint2.x, self.endPoint2.y);
CGContextStrokePath(context2);
}
or is there a better way to handle drawing my lines?
EDIT: the end goal is to use it with a UIView with labels and textboxes i'm using as a form. i want lines to break up the form.
i'm wondering if i should just use these custom draw2D UIViews as lines only and place them on top of the UIView form, then i can just create multiple "draw2D" UIViews?
SOLUTION:
so here is the code i figured out that would work:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] init];
[tempDict setValue:[NSValue valueWithCGPoint:CGPointMake(10, 10)] forKey:#"start"];
[tempDict setValue:[NSValue valueWithCGPoint:CGPointMake(500, 500)] forKey:#"end"];
[tempArray addObject:tempDict];
NSMutableDictionary *tempDict2 = [[NSMutableDictionary alloc] init];
[tempDict2 setValue:[NSValue valueWithCGPoint:CGPointMake(400, 10)] forKey:#"start"];
[tempDict2 setValue:[NSValue valueWithCGPoint:CGPointMake(500, 500)] forKey:#"end"];
[tempArray addObject:tempDict2];
self.pointArray = [NSArray arrayWithArray:tempArray];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
for (int i = 0; i < [self.pointArray count]; i++)
{
CGPoint tempPoint = [[[self.pointArray objectAtIndex:i] objectForKey:#"start"] CGPointValue];
CGPoint tempPoint2 = [[[self.pointArray objectAtIndex:i] objectForKey:#"end"] CGPointValue];
CGContextMoveToPoint(context, tempPoint.x, tempPoint.y);
CGContextAddLineToPoint(context, tempPoint2.x, tempPoint2.y);
}
CGContextStrokePath(context);
}
i included my initWithFrame: method on creating the array that i used for testing (CGPoint's are not objects, so had to figure out how to include them in an dictionary).
so this will loop through and create n lines.

You can just repeat this bit as many times as you need (with different coordinates each time obviously).
CGContextMoveToPoint(context, self.startPoint.x, self.startPoint.y);
CGContextAddLineToPoint(context, self.endPoint.x, self.endPoint.y);
The first line of code sets the start point of the line, the second line of code draws it. You don't need to repeat all the rest of the code.
In other words, you can draw multiple lines like this:
//set up context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, self.lineWidth1);
CGContextSetStrokeColorWithColor(context, self.lineColor1.CGColor);
//line1
CGContextMoveToPoint(context, self.startPoint1.x, self.startPoint1.y);
CGContextAddLineToPoint(context, self.endPoint1.x, self.endPoint1.y);
//line 2
CGContextMoveToPoint(context, self.startPoint2.x, self.startPoint2.y);
CGContextAddLineToPoint(context, self.endPoint2.x, self.endPoint2.y);
//line 3
etc...
//finished drawing
CGContextStrokePath(context);
You don't need to copy the whole lot each time you draw a line, you can just repeat those two lines of code.

Well, you could refactor out the duplicate code into another method and pass in the variables it needs to draw a line, i.e. the context, colour, start and end points, width

the better and universal way is:
- (void)drawRect:(CGRect)rect:(long)lineWidth:(CGColor)color:(CGPoint)pStart:(CGPoint)pEnd
{
//line1
CGContextRef context = UIGraphicsGetCurrentContext();
NSLog(#"drawRect called");
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, color);
CGContextMoveToPoint(context, pStart.x, pStart.y);
CGContextAddLineToPoint(context, pEnd.x, pEnd.y);
CGContextStrokePath(context);
}
...
[self drawRect:rect:self.lineWidth1:self.lineColor1.CGColor:self.startPoint1:self.endPoint1];
[self drawRect:rect:self.lineWidth2:self.lineColor2.CGColor:self.startPoint2:self.endPoint2];
...

Related

Objective-C - How to draw perpendicular lines at center

This seems like a dumb question or maybe I'm just tired but I'm not getting the results I want. I'm not sure I'm getting any results since I can't see my lines. Can someone please tell me what I am doing wrong?
- (void)drawCenterPlus
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 20);
[[UIColor redColor] setStroke];
CGPoint hStart;
CGPoint hEnd;
CGPoint vStart;
CGPoint vEnd;
hStart.x = self.center.x - 20.0;
hStart.y = self.center.y;
hEnd.x = self.center.x + 20.0;
hEnd.y = self.center.y;
vStart.x = self.center.x;
vStart.y = self.center.y - 20.0;
vEnd.x = self.center.x;
vEnd.y = self.center.y + 20.0;
//line 1
CGContextBeginPath(context);
CGContextMoveToPoint(context, hStart.x, hStart.y);
CGContextAddLineToPoint(context, hEnd.x, hEnd.y);
CGContextStrokePath(context);
//line 2
CGContextBeginPath(context);
CGContextMoveToPoint(context, vStart.x, vStart.y);
CGContextAddLineToPoint(context, vEnd.x, vEnd.y);
CGContextStrokePath(context);
}
Thank you in advance.
------------------------------------------------------- Additional Info ------------------------------------------
Actually this is called from viewDidLoad. I am just trying to draw a "+" in the center of the view. I've made the view a different color, so that the "+" can be seen. The "+" can be drawn in black, I just want to be able to see it. Yes, I could put a text "+" on the screen but ultimately I need to draw it inside of a rectangle.
I'm going to try again with a blank view to see if the drawing is hiding behind a subview. We'll see. Thank you again for your help.
------------------------------------------------------- Latest Method -----------------------------------------------
OK. Just a new project with one view that only has (hopefully) the red "+" in the middle of the screen. But nothing displays. What am I doing wrong?
#implementation TESTViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 20);
[[UIColor redColor] setStroke];
CGPoint hStart;
CGPoint hEnd;
CGPoint vStart;
CGPoint vEnd;
hStart.x = self.view.center.x - 20.0;
hStart.y = self.view.center.y;
hEnd.x = self.view.center.x + 20.0;
hEnd.y = self.view.center.y;
vStart.x = self.view.center.x;
vStart.y = self.view.center.y - 20.0;
vEnd.x = self.view.center.x;
vEnd.y = self.view.center.y + 20.0;
//line 1
CGContextBeginPath(context);
CGContextMoveToPoint(context, hStart.x, hStart.y);
CGContextAddLineToPoint(context, hEnd.x, hEnd.y);
CGContextStrokePath(context);
//line 2
CGContextBeginPath(context);
CGContextMoveToPoint(context, vStart.x, vStart.y);
CGContextAddLineToPoint(context, vEnd.x, vEnd.y);
CGContextStrokePath(context);
}
Check the value of context - I'm guessing it may be nil.
If you put this code inside a UIView subclass's drawRect method it should work as you will have a valid context.
Alternatively, you could create your own UIImage context, draw the shape in that and add the image as a subview. E.g.
- (void)drawCenterPlus {
UIGraphicsBeginImageContext(self.view.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 20);
[[UIColor redColor] setStroke];
CGPoint hStart;
CGPoint hEnd;
CGPoint vStart;
CGPoint vEnd;
hStart.x = self.view.center.x - 20.0;
hStart.y = self.view.center.y;
hEnd.x = self.view.center.x + 20.0;
hEnd.y = self.view.center.y;
vStart.x = self.view.center.x;
vStart.y = self.view.center.y - 20.0;
vEnd.x = self.view.center.x;
vEnd.y = self.view.center.y + 20.0;
//line 1
CGContextBeginPath(context);
CGContextMoveToPoint(context, hStart.x, hStart.y);
CGContextAddLineToPoint(context, hEnd.x, hEnd.y);
CGContextStrokePath(context);
//line 2
CGContextBeginPath(context);
CGContextMoveToPoint(context, vStart.x, vStart.y);
CGContextAddLineToPoint(context, vEnd.x, vEnd.y);
CGContextStrokePath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
}
CGContextRef context = UIGraphicsGetCurrentContext(); will be nil at this point... you basically want to add this code to your drawing code, unless you want to add static views or images to represent the cross.
The current Context wont be your view object unless it is has focus, which happens automatically by the time the drawRect: method is called.
you can alternately draw to an image and add it to an image view, you can do that in did load, and the drawing will happen automatically from that point on.

drawRect drawing 'transparent' text?

I am looking to draw a UILabel (preferable through subclassing) as a transparent label, but with solid background. I draw up an quick example (sorry, it's ugly, but it gets the points across :)).
Basically I have a UILabel and I would like the background to be a set colour, and the text should be transparent. I do not want to colour the text with the views background, but instead have it be 100% transparent, since I have a texture in the background that I want to make sure lines up inside and outside of the label.
I've been spending the night browsing SO and searching on Google, but I have found no helpful sources. I don't have much experience with CG drawing, so I would appreciate any links, help, tutorial or sample code (maybe Apple has some I need to have a look at?).
Thanks a bunch!
I've rewritten it as a UILabel subclass using barely any code and posted it on GitHub
The gist of it is you override drawRect but call [super drawRect:rect] to let the UILabel render as normal. Using a white label color lets you easily use the label itself as a mask.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// let the superclass draw the label normally
[super drawRect:rect];
CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));
// create a mask from the normally rendered text
CGImageRef image = CGBitmapContextCreateImage(context);
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerComponent(image), CGImageGetBitsPerPixel(image), CGImageGetBytesPerRow(image), CGImageGetDataProvider(image), CGImageGetDecode(image), CGImageGetShouldInterpolate(image));
CFRelease(image); image = NULL;
// wipe the slate clean
CGContextClearRect(context, rect);
CGContextSaveGState(context);
CGContextClipToMask(context, rect, mask);
CFRelease(mask); mask = NULL;
[self RS_drawBackgroundInRect:rect];
CGContextRestoreGState(context);
}
Solved using CALayer masks. Creating a standard mask (wallpapered text, for example) is simple. To create the knocked-out text, I had to invert the alpha channel of my mask, which involved rendering a label to a CGImageRef and then doing some pixel-pushing.
Sample application is available here: https://github.com/robinsenior/RSMaskedLabel
Relevant code is here to avoid future link-rot:
#import "RSMaskedLabel.h"
#import <QuartzCore/QuartzCore.h>
#interface UIImage (RSAdditions)
+ (UIImage *) imageWithView:(UIView *)view;
- (UIImage *) invertAlpha;
#end
#interface RSMaskedLabel ()
{
CGImageRef invertedAlphaImage;
}
#property (nonatomic, retain) UILabel *knockoutLabel;
#property (nonatomic, retain) CALayer *textLayer;
- (void) RS_commonInit;
#end
#implementation RSMaskedLabel
#synthesize knockoutLabel, textLayer;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self RS_commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self RS_commonInit];
}
return self;
}
+ (Class)layerClass
{
return [CAGradientLayer class];
}
- (void) RS_commonInit
{
[self setBackgroundColor:[UIColor clearColor]];
// create the UILabel for the text
knockoutLabel = [[UILabel alloc] initWithFrame:[self frame]];
[knockoutLabel setText:#"booyah"];
[knockoutLabel setTextAlignment:UITextAlignmentCenter];
[knockoutLabel setFont:[UIFont boldSystemFontOfSize:72.0]];
[knockoutLabel setNumberOfLines:1];
[knockoutLabel setBackgroundColor:[UIColor clearColor]];
[knockoutLabel setTextColor:[UIColor whiteColor]];
// create our filled area (in this case a gradient)
NSArray *colors = [[NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.349 green:0.365 blue:0.376 alpha:1.000] CGColor],
(id)[[UIColor colorWithRed:0.455 green:0.490 blue:0.518 alpha:1.000] CGColor],
(id)[[UIColor colorWithRed:0.412 green:0.427 blue:0.439 alpha:1.000] CGColor],
(id)[[UIColor colorWithRed:0.208 green:0.224 blue:0.235 alpha:1.000] CGColor],
nil] retain];
NSArray *gradientLocations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.54],
[NSNumber numberWithFloat:0.55],
[NSNumber numberWithFloat:1], nil];
// render our label to a UIImage
// if you remove the call to invertAlpha it will mask the text
invertedAlphaImage = [[[UIImage imageWithView:knockoutLabel] invertAlpha] CGImage];
// create a new CALayer to use as the mask
textLayer = [CALayer layer];
// stick the image in the layer
[textLayer setContents:(id)invertedAlphaImage];
// create a nice gradient layer to use as our fill
CAGradientLayer *gradientLayer = (CAGradientLayer *)[self layer];
[gradientLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[gradientLayer setColors: colors];
[gradientLayer setLocations:gradientLocations];
[gradientLayer setStartPoint:CGPointMake(0.0, 0.0)];
[gradientLayer setEndPoint:CGPointMake(0.0, 1.0)];
[gradientLayer setCornerRadius:10];
// mask the text layer onto our gradient
[gradientLayer setMask:textLayer];
}
- (void)layoutSubviews
{
// resize the text layer
[textLayer setFrame:[self bounds]];
}
- (void)dealloc
{
CGImageRelease(invertedAlphaImage);
[knockoutLabel release];
[textLayer release];
[super dealloc];
}
#end
#implementation UIImage (RSAdditions)
/*
create a UIImage from a UIView
*/
+ (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
/*
get the image to invert its alpha channel
*/
- (UIImage *)invertAlpha
{
// scale is needed for retina devices
CGFloat scale = [self scale];
CGSize size = self.size;
int width = size.width * scale;
int height = size.height * scale;
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *memoryPool = (unsigned char *)calloc(width*height*4, 1);
CGContextRef context = CGBitmapContextCreate(memoryPool, width, height, 8, width * 4, colourSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colourSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self CGImage]);
for(int y = 0; y < height; y++)
{
unsigned char *linePointer = &memoryPool[y * width * 4];
for(int x = 0; x < width; x++)
{
linePointer[3] = 255-linePointer[3];
linePointer += 4;
}
}
// get a CG image from the context, wrap that into a
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage *returnImage = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationUp];
// clean up
CGImageRelease(cgImage);
CGContextRelease(context);
free(memoryPool);
// and return
return returnImage;
}
#end
Here's a technique that's similar to Matt Gallagher's, which will generate an inverted text mask with an image.
Allocate a (mutable) data buffer. Create a bitmap context with an 8-bit alpha channel. Configure settings for text drawing. Fill the whole buffer in copy mode (default colour assumed to have alpha value of 1). Write the text in clear mode (alpha value of 0). Create an image from the bitmap context. Use the bitmap as a mask to make a new image from the source image. Create a new UIImage and clean up.
Every time the textString or sourceImage or size values change, re-generate the final image.
CGSize size = /* assume this exists */;
UIImage *sourceImage = /* assume this exists */;
NSString *textString = /* assume this exists */;
char *text = [textString cStringUsingEncoding:NSMacOSRomanStringEncoding];
NSUInteger len = [textString lengthOfBytesUsingEncoding:cStringUsingEncoding:NSMacOSRomanStringEncoding];
NSMutableData *data = [NSMutableData dataWithLength:size.width*size.height*1];
CGContextRef context = CGBitmapContextCreate([data mutableBytes], size.width, size.height, 8, size.width, NULL, kCGImageAlphaOnly);
CGContextSelectFont(context, "Gill Sans Bold", 64.0f, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextFillRect(context, overlay.bounds);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextShowTextAtPoint(context, 16.0f, 16.0f, text, len);
CGImageRef textImage = CGBitmapContextCreateImage(context);
CGImageRef newImage = CGImageCreateWithMask(sourceImage.CGImage, textImage);
UIImage *finalImage = [UIImage imageWithCGImage:newImage];
CGContextRelease(context);
CFRelease(newImage);
CFRelease(textImage);
Another way to do this involves putting the textImage into a new layer and setting that layer on your view's layer. (Remove the lines that create "newImage" and "finalImage".) Assuming this happens inside your view's code somewhere:
CALayer *maskLayer = [[CALayer alloc] init];
CGPoint position = CGPointZero;
// layout the new layer
position = overlay.layer.position;
position.y *= 0.5f;
maskLayer.bounds = overlay.layer.bounds;
maskLayer.position = position;
maskLayer.contents = (__bridge id)textImage;
self.layer.mask = maskLayer;
There are more alternatives, some might be better (subclass UIImage and draw the text directly in clear mode after the superclass has done its drawing?).
Swift 5 solution (Xcode: 12.5):
class MaskedLabel: UILabel {
var maskColor : UIColor?
override init(frame: CGRect) {
super.init(frame: frame)
customInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
customInit()
}
func customInit() {
maskColor = self.backgroundColor
self.textColor = UIColor.white
backgroundColor = UIColor.clear
self.isOpaque = false
}
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
super.draw(rect)
context.concatenate(__CGAffineTransformMake(1, 0, 0, -1, 0, rect.height))
let image: CGImage = context.makeImage()!
let mask: CGImage = CGImage(maskWidth: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bitsPerPixel: image.bitsPerPixel, bytesPerRow: image.bytesPerRow, provider: image.dataProvider!, decode: image.decode, shouldInterpolate: image.shouldInterpolate)!
context.clear(rect)
context.saveGState()
context.clip(to: rect, mask: mask)
if (self.layer.cornerRadius != 0.0) {
context.addPath(CGPath(roundedRect: rect, cornerWidth: self.layer.cornerRadius, cornerHeight: self.layer.cornerRadius, transform: nil))
context.clip()
}
drawBackgroundInRect(rect: rect)
context.restoreGState()
}
func drawBackgroundInRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
if let _ = maskColor {
maskColor!.set()
}
context!.fill(rect)
}
}

Adding an NSRect when a button is pushed

I am trying to add an NSRect when a button is pushed but IBAction is in one view controller while the drawing code must be in a view and for some reason the code I am using won't draw the rect here is the source for the action method in the view controller:
-(IBAction)ButtonPressed:(id)sender {
NSRect Rect = NSMakeRect(10, 10, 100, 100);
NSColor* BlackFill = [NSColor blackColor];
[BlackFill set];
NSRectFill(Rect);
NSColor* whitestroke = [NSColor whiteColor];
[whitestroke set];
NSFrameRectWithWidth(Rect, 5.0);
[rects addObject:[NSValue valueWithRect:Rect]];
[self.rectView setNeedsDisplay:YES];
}
And the source for the drawing code in the view:
-(void)drawRect:(NSRect)dirtyRect {
//heres the part where I want to draw the NSRect but it does not work
if ([datasource conformsToProtocol:#protocol(MainViewDatasource)]) {
NSLog(#"DataSource conforms to protocol:MainViewDatasource");
NSUInteger numRects = [datasource numberOfRectsInView:self];
for (NSUInteger i = 0; i < numRects < 800; i++) {
NSRect currentRect = [datasource rectangleView:self rectAtIndex:i];
NSFrameRect(currentRect);
}
if (numRects >= 800) {
NSAlert* alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSInformationalAlertStyle];
[alert setMessageText:#"You have placed too many rectangle shapes in your level"];
[alert addButtonWithTitle:#"OK"];
[alert release];
}
}
/* The code here works great but has nothing to do with adding a rect when the button is pressed */
NSRect Rect = NSMakeRect(0.0, 0.0, 7000.0, 3500.0);
int width = Rect.size.width;
int height = Rect.size.height;
int i = 0;
[[NSColor blackColor] set];
NSBezierPath* drawingPath = [NSBezierPath bezierPath];
for (i=0; i<=width; i=i+GRIDSIZE) {
[drawingPath moveToPoint:NSMakePoint(i, 0)];
[drawingPath lineToPoint:NSMakePoint(i, height)];
}
for (i=0; i<=height; i=i+GRIDSIZE) {
[drawingPath moveToPoint:NSMakePoint(0, i)];
[drawingPath lineToPoint:NSMakePoint(width, i)];
}
[drawingPath stroke];
}
Thank you in advance for your help.
It sounds very much like your datasource is nil. A few possibilities:
You forgot to hook it up in Interface Builder
It isn't connected through Interface Builder, but through another controller — but that class (the controller) has a missing connection in Interface Builder
You simply forgot to assign it
Trace forward from where the datasource is supposed to be set to figure out where the chain of custody disappears.

Display NSImage on a CALayer

I've been trying to display a NSImage on a CALayer. Then I realised I need to convert it to a CGImage apparently, then display it...
I have this code which doesn't seem to be working
CALayer *layer = [CALayer layer];
NSImage *finderIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFinderIcon)];
[finderIcon setSize:(NSSize){ 128.0f, 128.0f }];
CGImageSourceRef source;
source = CGImageSourceCreateWithData((CFDataRef)finderIcon, NULL);
CGImageRef finalIcon = CGImageSourceCreateImageAtIndex(source, 0, NULL);
layer.bounds = CGRectMake(128.0f, 128.0f, 4, 4);
layer.position = CGPointMake(128.0f, 128.0f);
layer.contents = finalIcon;
// Insert the layer into the root layer
[mainLayer addSublayer:layer];
Why? How can I get this to work?
From the comments: Actually, if you're on 10.6, you can also just set the CALayer's contents to an NSImage rather than a CGImageRef...
If you're on OS X 10.6 or later, take a look at NSImage's CGImageForProposedRect:context:hints: method.
If you're not, I've got this in a category on NSImage:
-(CGImageRef)CGImage
{
CGContextRef bitmapCtx = CGBitmapContextCreate(NULL/*data - pass NULL to let CG allocate the memory*/,
[self size].width,
[self size].height,
8 /*bitsPerComponent*/,
0 /*bytesPerRow - CG will calculate it for you if it's allocating the data. This might get padded out a bit for better alignment*/,
[[NSColorSpace genericRGBColorSpace] CGColorSpace],
kCGBitmapByteOrder32Host|kCGImageAlphaPremultipliedFirst);
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:bitmapCtx flipped:NO]];
[self drawInRect:NSMakeRect(0,0, [self size].width, [self size].height) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapCtx);
CGContextRelease(bitmapCtx);
return (CGImageRef)[(id)cgImage autorelease];
}
I think I wrote this myself. But it's entirely possible that I ripped it off from somewhere else like Stack Overflow. It's an older personal project and I don't really remember.
Here's some code which may help you - I sure hope the formatting of this does not get all messed up like it appears is going to happen - all I can offer is that this works for me.
// -------------------------------------------------------------------------------------
- (void)awakeFromNib
{
// setup our main window 'contentWindow' to use layers
[[contentWindow contentView] setWantsLayer:YES]; // NSWindow*
// create a root layer to contain all of our layers
CALayer *root = [[contentWindow contentView] layer];
// use constraint layout to allow sublayers to center themselves
root.layoutManager = [CAConstraintLayoutManager layoutManager];
// create a new layer which will contain ALL our sublayers
// -------------------------------------------------------
mContainer = [CALayer layer];
mContainer.bounds = root.bounds;
mContainer.frame = root.frame;
mContainer.position = CGPointMake(root.bounds.size.width * 0.5,
root.bounds.size.height * 0.5);
// insert layer on the bottom of the stack so it is behind the controls
[root insertSublayer:mContainer atIndex:0];
// make it resize when its superlayer does
root.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
// make it resize when its superlayer does
mContainer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
}
// -------------------------------------------------------------------------------------
- (void) loadMyImage:(NSString*) path
n:(NSInteger) num
x:(NSInteger) xpos
y:(NSInteger) ypos
h:(NSInteger) hgt
w:(NSInteger) wid
b:(NSString*) blendstr
{
#ifdef __DEBUG_LOGGING__
NSLog(#"loadMyImage - ENTER [%#] num[%d] x[%d] y[%d] h[%d] w[%d] b[%#]",
path, num, xpos, ypos, hgt, wid, blendstr);
#endif
NSInteger xoffset = ((wid / 2) + xpos); // use CORNER versus CENTER for location
NSInteger yoffset = ((hgt / 2) + ypos);
CIFilter* filter = nil;
CGRect cgrect = CGRectMake((CGFloat) xoffset, (CGFloat) yoffset,
(CGFloat) wid, (CGFloat) hgt);
if(nil != blendstr) // would be equivalent to #"CIMultiplyBlendMode" or similar
{
filter = [CIFilter filterWithName:blendstr];
}
// read image file via supplied path
NSImage* theimage = [[NSImage alloc] initWithContentsOfFile:path];
if(nil != theimage)
{
[self setMyImageLayer:[CALayer layer]]; // create layer
myImageLayer.frame = cgrect; // locate & size image
myImageLayer.compositingFilter = filter; // nil is OK if no filter
[myImageLayer setContents:(id) theimage]; // deposit image into layer
// add new layer into our main layer [see awakeFromNib above]
[mContainer insertSublayer:myImageLayer atIndex:0];
[theimage release];
}
else
{
NSLog(#"ERROR loadMyImage - no such image [%#]", path);
}
}
+ (CGImageRef) getCachedImage:(NSString *) imageName
{
NSGraphicsContext *context = [[NSGraphicsContext currentContext] graphicsPort];
NSImage *img = [NSImage imageNamed:imageName];
NSRect rect = NSMakeRect(0, 0, [img size].width, [img size].height);
return [img CGImageForProposedRect:&rect context:context hints:NULL];
}
+ (CGImageRef) getImage:(NSString *) imageName withExtension:(NSString *) extension
{
NSGraphicsContext *context = [[NSGraphicsContext currentContext] graphicsPort];
NSString* imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:extension];
NSImage* img = [[NSImage alloc] initWithContentsOfFile:imagePath];
NSRect rect = NSMakeRect(0, 0, [img size].width, [img size].height);
CGImageRef imgRef = [img CGImageForProposedRect:&rect context:context hints:NULL];
[img release];
return imgRef;
}
then you can set it:
yourLayer.contents = (id)[self getCachedImage:#"myImage.png"];
or
yourLayer.contents = (id)[self getImage:#"myImage" withExtension:#"png"];

Infinite source rectangle to drawImage method

Why do i always get this log, Infinite source rectangle to drawImage method, is this such an error or there's something wrong with the code algorithm. I don't really understand about this, and i can't find any useful information on google. Hope i can find the answer on this site.
sorry, this is my drawRect method :
- (void)drawRect:(NSRect)rect {
// Drawing code here.
// drawBackground
[[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:1.0] set];
[NSBezierPath fillRect:[self bounds]];
if (myCIImage != nil) {
//Create the CIContext
CIContext *context = [[NSGraphicsContext currentContext] CIContext];
//scale to fit in view rect
CIImage *drawnImage;
if (previewFilter == YES) {
drawnImage = [previewImage imageByApplyingTransform:[self imageTransformToFitView]];
} else {
drawnImage = [myCIImage imageByApplyingTransform:[self imageTransformToFitView]];
}
// center in view rect
NSRect viewBounds = [self bounds];
CGRect sRect = [drawnImage extent];
CGRect dRect = sRect;
dRect.origin.x = viewBounds.origin.x + (viewBounds.size.width - sRect.size.width)/2;
dRect.origin.y = viewBounds.origin.y + (viewBounds.size.height - sRect.size.height)/2;
[context drawImage:drawnImage
inRect:dRect
fromRect:sRect];
}
}
thanks -
I think that means the dimensions of sRect contain some infinite numbers. Have you run this under the debugger and inspected the values in sRect?
Probably the transform you apply to the image is wrong.