I am a complete newbie to XCode, so I have been climbing the Quartz2D learning curve. I understand that a view's drawRect method is called whenever a refresh of the view's graphics is needed, and that the setNeedsDisplay method activates a redrawing.
But what I can't find is a clear explanation of the relationship between the graphics context and a specific view. The graphics context itself is apparently not an instance variable of the view, so if I want to modify a view, and I create a complex path using the CGContext... methods, what code is needed to marry that graphics context to the view I wish to alter?
Thanks in advance for any guidance on this question.
jrdoner
You can create a graphics context but that is needed only in complex drawing operation.For most of the cases it is done for you. You just need to get the context by calling UIGraphicsGetCurrentContext().
When the framework determines that a view needs redrawing (for various reasons, one of which is that you indicated it by calling setNeedsDisplay:), it will generate (or restore) a graphics context for that view, and make it the current context before calling -drawRect:. Your job is then to draw in the context you've been provided. Afterwards, it is the framework's problem to clip the resulting context, blend it with other contexts and finally draw it into screen memory.
Do be a little careful of doing too much complex drawing in -drawRect: if you can help it. The iPhone doesn't have nearly as powerful a CPU as a desktop machine, and it is recommended that you do most of your drawing work using images rather than paths. Apple has even removed many of the more convenient drawing wrappers from Mac, almost intentionally dissuading developers from using Core Graphics too much.
I assume that you are creating your path within -(void)drawRect:(CGRect)rect method of your UIView subclass.
Inside drawRect you can get access to graphic context by calling (CGContextRef)UIGraphicsGetCurrentContext(void);
Example from lecture 5 cs193p:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor grayColor] set];
UIRectFill ([self bounds]);
CGContextBeginPath (context);
CGContextMoveToPoint (context, 75, 10);
CGContextAddLineToPoint (context, 10, 150);
CGContextAddLineToPoint (context, 160, 150);
CGContextClosePath (context);
[[UIColor redColor] setFill];
[[UIColor blackColor] setStroke];
CGContextDrawPath (context, kCGPathFillStroke);
}
Related
I have a variable sized UIView with a drawRect containing CoreGraphics custom drawing code. The view was originally quite small but has the potential to become very wide and is placed inside a UIScrollView to enable the user to scroll through the view.
At the moment the view is created to be as wide as it needs to be to fit the entire drawing on and the CoreGraphics code draws across the entire view in drawRect.
- (void)drawRect:(CGRect)rect {
// Only try and draw if the frame size is greater than zero and the graph has some range
if ((self.frame.size.width>0) && (self.frame.size.height>0) && ((maxX - minX)>0)) {
// Get the current drawing context
CGContextRef context = UIGraphicsGetCurrentContext();
// Save the state of the graphics context
CGContextSaveGState(context);
// Set the line style
CGContextSetRGBStrokeColor(context, 0.44, 0.58, 0.77, 1.0);
CGContextSetRGBFillColor(context, 0.44, 0.58, 0.77, 1.0);
CGContextSetLineWidth(context, 3.0);
CGContextSetLineDash(context, 0, NULL, 0);
// Draw the graph
[self drawGraphInContext:context];
// Restore the graphics context
CGContextRestoreGState(context);
}
}
I can make this more efficient by:
Doing bounding calcs with the rect parameter in the drawRect and only drawing stuff that is currently visible.
Creating a smaller UIView (say 2x the width of the scrollView frame) and as the scroll position moves, redraw the image and re-centre the scrollView.
Before I go to the effort of doing either of these things, I would like to understand if it is worth the effort and whether I could run into problems later on if the UIView becomes very wide. So far performance is not an issue, but I am worried that memory might be.
Specifically, does the scrollView limit the amount of memory used by the view to the visible area? is the graphics context created the size of the entire view, the visible area or the size of the rect parameter? Are drawing operations outside the visible area simply ignored by CoreGraphics?
Any help and best practice advice would be appreciated.
IMO, it all depends on the rect argument to the call:
- (void)drawRect:(CGRect)rect
and as far as I know, this will be set to the contentSize of your scroll view. Simply, the context and the memory used for it will be related to that rect value.
You might trying doing your own "clipping" of the area you draw in by taking into account the frame of the scroll view.
Better yet, you could have a look at a sample by Apple about tiling content in a UIScrollView. It is sample 3.
Is there a way to create a colored fill pattern dynamically in Cocoa?
In particular instead of using a fixed pattern from an image file via
NSColor *fillPattern = [NSColor colorWithPatternImage:patternImage];
I'd like to create a pattern by dynamically choosing the appropriate colors at runtime.
Background is highlighting a colored object by rendering stripes or squares in the ''opposite'' color on top of it - whatever opposite might mean in this context, but that's a different story..
Being applied to potentially hundreds of objects in a drawing app it needs to be a rather fast method so I suppose just swapping colors in patternImage won't be good enough.
(It did work just fine back in QuickDraw..!)
Why not just draw to an in-memory image and use that for your pattern?
NSImage* patternImage = [[NSImage alloc] initWithSize:someSize];
[patternImage lockFocus];
//draw your pattern
[patternImage unlockFocus];
NSColor* patternColor = [NSColor colorWithPatternImage:patternImage];
//do something with the pattern color
//remember to release patternImage if you're not using ARC
Performance-wise, you generally should be looking at optimising drawing by paying attention to the rect passed in to drawRect: and making sure you only draw what is necessary. If you do that then I can't see the pattern drawing performance being a major problem.
Background is highlighting a colored object by rendering stripes or squares in the ''opposite'' color on top of it - whatever opposite might mean in this context, but that's a different story..
You'll want to use one of Quartz's blend modes (most of them are present in Photoshop, Pixelmator, and Opacity, so you can experiment in one of those apps to determine which one you need).
You should then be able to fill with a static image—or a dynamic pattern, if it's really necessary—and Quartz will blend it in appropriately.
There's no way to do this in AppKit alone; you'll need to get a CGContext from the current NSGraphicsContext and do it in Quartz.
I've got this giant UIView with around 600 UIButtons as subviews. The UIView is about 2000 by 2000 pixels and it's a subview on itself for a UIScrollView. I'm trying to implement CATiledLayer as the rendering layer for this giant UIView but I can't seem to figure out how to render the tiles. I found a lot of examples that cover CATiledLayer with tiled images, pdf's, ... but I never found a real example on how to draw a complete UIView with a lot of subviews. You're probably asking why i'd like to stick with UIView? Because I'd like the users to keep on using the buttons to interact with the view.
I'm wondering if someone has an example or some psuedo code on how to implement the - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context method, keeping in mind that there's 1 giant UIView with a lot of UIbuttons as its subviews.
Starting with iOS 4.0, you can use drawRect: instead of drawLayer:inContext:. This Q&A from Apple explains it: http://developer.apple.com/library/ios/#qa/qa1637/_index.html. The important point from it is that drawRect: in 4 is now thread-safe and you can use UIKit graphics functionality.
You still need to override this method to use a CATileLayer:
+(Class)layerClass
{
return [CATiledLayer class];
}
But you can now just use drawRect:
-(void)drawRect:(CGRect)rect
{
//normal drawing routines here.
}
The rect that is delivered to the method will be the size of your tiles and you need to determine what needs to be drawn in the particular rect. The power of CATiledLayer is that it only calls for drawing the tiles that are showing on the screen. It also uses background threads and is very fast.
I have not used CATiledLayer with UIViews, only large images. Are the UIViews subclasses with their own drawRect: implementations? I think you will need to determine which views will be showing in the current rect and call their drawRect: method.
Using drawRect: is only for iOS 4.0 and later, it won't work for older versions.
Warning: I'm a Cocoa newbie.
I'm reading "Cocoa Programming For Mac OS X" by Hillegass.
On p.301 it's written:
To make the drawing appear on the image instead of on the screen, you must first lock focus on the image. When the drawing is complete, you must unlock focus.
The code I have, inside -(void)mouseDragged:(NSEvent *)theEvent of an NSView is as follows:
[resizedImage lockFocus];
[sourceImage drawInRect: NSMakeRect(0, 0, resizeWidth, resizeHeight) fromRect: NSMakeRect(0, 0, originalSize.width, originalSize.height) operation: NSCompositeSourceOver fraction: 1.0];
[resizedImage unlockFocus];
Without the lock/unlock, this does not work, but I still don't understand exactly what is going on.
I see that the 2nd line of code has no mention of resizedImage so does that mean when I use lockFocus it makes sure any 'drawing' that happens takes place there? Could someone explain this better?
Drawing requires a 'graphics context'. You'll notice that, unlike Core Graphics, none of the AppKit drawing methods take a parameter that specifies where the drawing ends up. Instead, the destination is stored globally as [NSGraphicsContext currentContext]. All AppKit drawing methods affect this current context.
The main purpose of -lockFocus (on images and views alike) is to set up the graphics context so your drawing ends up going where you want it to.
From the docs for -[NSImage lockFocus]:
This method sets the current drawing context to the area of the offscreen window used to cache the receiver's contents.
So there exists an offscreen window which you draw on when you draw to the image. This image has a graphics context and lockFocus makes this context the current drawing context so that drawInRect:... uses it for its drawing. It's similar to +[NSGraphicsContext setCurrentContext].
In core animation or in App kit When we say layer-backed view or simply add a layer in the view,then actually what we mean by the layer.
A simple Google search:
The CALayer is the canvas upon which everything in Core Animation is painted. When you define movement, color changes, image effects, and so on, those are applied to CALayer objects. From a code perspective, CALayers are a lightweight representation similar to an NSView. In fact, NSView objects can be manipulated via their CALayer. This is referred to as being layer-backed.
A CALayer is an object which manages and draws upon a GL surface, and can manipulate that surface's location in three dimensions, without needing to redraw its contents.