I'm working on creating my first custom UIView and learning about the semantics of the drawRect method.
I have encountered advice (advice that makes sense to me) to only draw within the scope of the CGRect that is passed in as an argument, rather than always drawing everything within the UIView's bounds:
- (void)drawRect:(CGRect)aRect {
// draw, draw, draw...
}
What I'm trying to find out now is whether there are any constraints about what relationship there is between bounds and the aRect that gets passed in. In particular, I'm wondering if aRect is guaranteed to be entirely inside bounds, or whether it may extends outside of bounds.
If some code outside of my UIView passes, for example, a very large CGRect into setNeedsDisplayInRect:(NSRect)invalidRect, will the underlying code just blindly pass the same CGRect into my drawRect method, or does it do some sort of intersection and always pass in a sensible rectangle?
I haven't found an answer to this in any documentation. Is this something I shouldn't even worry about? Or should I always intersect bounds and aRect on my own?
From the View Programming Guide:
Before calling your view’s drawRect: method, UIKit configures the basic drawing environment for your view. Specifically, it creates a graphics context and adjusts the coordinate system and clipping region to match the coordinate system and visible bounds of your view.
You won't be asked to draw outside your view. Even if you were, the clip rect should be set such that the drawing would have no effect.
Related
I found in git some bar graph project, that I would like to work with: here
The code is pretty simple. The draw object has only 3 methods, like init, calc, and drawRect.
The funny point is, I can not find the line, that call this method: drawRect.
I looked in all the classes. It looks to start automatically, but why? How do I know which methods will start automatically, and which not?
Have you read the Apple Documentation on UIView? It specifically states
This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should NEVER call this method directly yourself. To invalidate part of your view, and thus cause that portion to be redrawn, call the setNeedsDisplay or setNeedsDisplayInRect: method instead.
So you should never actually call that method directly it will get called by the super versions of setNeedsDisplay or setNeedsDisplayInRect. I have included the whole section of the article below which I would recommend reading.
The default implementation of this method does nothing. Subclasses that use technologies such as Core Graphics and UIKit to draw their view’s content should override this method and implement their drawing code there. You do not need to override this method if your view sets its content in other ways. For example, you do not need to override this method if your view just displays a background color or if your view sets its content directly using the underlying layer object.
By the time this method is called, UIKit has configured the drawing environment appropriately for your view and you can simply call whatever drawing methods and functions you need to render your content. Specifically, UIKit creates and configures a graphics context for drawing and adjusts the transform of that context so that its origin matches the origin of your view’s bounds rectangle. You can get a reference to the graphics context using the UIGraphicsGetCurrentContext function, but do not establish a strong reference to the graphics context because it can change between calls to the drawRect: method.
Similarly, if you draw using OpenGL ES and the GLKView class, GLKit configures the underlying OpenGL ES context appropriately for your view before calling this method (or the glkView:drawInRect: method of your GLKView delegate), so you can simply issue whatever OpenGL ES commands you need to render your content. For more information about how to draw using OpenGL ES, see OpenGL ES Programming Guide for iOS.
You should limit any drawing to the rectangle specified in the rect parameter. In addition, if the opaque property of your view is set to YES, your drawRect: method must totally fill the specified rectangle with opaque content.
If you subclass UIView directly, your implementation of this method does not need to call super. However, if you are subclassing a different view class, you should call super at some point in your implementation.
This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should never call this method directly yourself. To invalidate part of your view, and thus cause that portion to be redrawn, call the setNeedsDisplay or setNeedsDisplayInRect: method instead.
I got it.
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
So only this one Method will be activated automatically.
I have UIAlertView and I log it's frame and bounds using:
[self.alertView show];
NSLog(#"frame: %#", NSStringFromCGRect(self.alertView.frame));
NSLog(#"bounds: %#", NSStringFromCGRect(self.alertView.bounds));
I notice that the width and height of the frame and bounds is different. How is this possible?
From UIView's frame, bounds, center, origin, when to use what?
frame - this is the property you most often use for normal iPhone applications. most controls will be laid out relative to the "containing" control so the frame.origin will directly correspond to where the control needs to display, and frame.size will determine how big to make the control.
bounds - this property is not a positioning property, but defines the drawable area of the UIView "relative" to the frame. By default this property is usually (0, 0, width, height).
That being said, it's not surprising nor uncommon for the bounds size to be different from the frame size. Since in this case the bounds rect is smaller than the frame it simply means that only a part of the view is being drawn.
Also consider that UIAlertView view hierarchy it's much more complex than it appears and it also went through a big change with iOS 7.
To further remark this, here's a statement from the documentation
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
Which is a nice way to say: here be dragons!
How can I possibly use the rect argument passed in drawRect calls, am sure whenever setNeedsDisplayInRect is passed, drawRect method is called for particular region. for now I redraw the text and shapes for entire frame and not constraining to the rect part that needs to be redrawn and I have no idea on how effectively can I use the rect param.
Any insights on this will be helpful.
The rect is the region to redraw. In some cases UIKit will redraw only a "dirty" region of your view. You can optimize your code to detect that some of the drawing would occur outside this rect by using CGRectContainsPoint() and therefore not do it.
I'm struggling with setNeedsDisplay. I thought it was supposed to trigger calls of drawRect: for the view for which it is called and the hierarchy below that if it's within the view's bounds, but I'm not finding that to be the case. Here is my setup:
From the application delegate, I create a view whose size is a square that covers essentially the whole screen real estate. This view is called TrollCalendarView. There is not much that happens with TrollCalendarView except for a rotation triggered by the compass.
There are 7 subviews of TrollCalendarView called PlatformView intended to contain 2D draw objects arranged around the center of TrollCalendarView in a 7-sided arrangement. So when the iPad is rotated, these 7 views rotate such that they are always oriented with the cardinal directions.
Each of the PlatformView subviews contains 3 subviews called Tower. Each tower contains 2D draw objects implemented in drawRect:.
So, in summary, I have TrollCalendarView with empty drawRect:, and subviews PlatformView and Platformview -> Tower that each have drawRect implementations. Additionally, Tower lies within the bounds of Platform, and Platform lies within the bounds of TrollCalendarView.
In TrollCalendarView I've added a swipe recognizer. When I swipe happens, a property is updated, and I call [self setNeedsDisplay] but nothing seems to happen. I added NSLog entries to drawRect: method in each of these views, and only the TrollCalendarView drawRect: method is called. Ironically, that is the one view whose drawRect method will be empty.
There is no xib file.
What do I need to do to ensure the drawRect method in the other subviews is called? Is there documentation somewhere that describes all the nuances that could affect this?
I'm struggling with setNeedsDisplay. I thought it was supposed to trigger calls of drawRect for the view for which it is called and the hierarchy below that if it's within the view's bounds
No, that is not the case. Where did you get that idea?
-setNeedsDisplay: applies only to the view to which it is sent. If you need to invalidate other views, you need to add some code to send -setNeedsDisplay: to them, too. That's all there is to it.
I think this is an optimization in the framework; if your subviews don't need to draw again, then this is a major performance improvement. Realize that almost anything animatable does not require drawrect (moving, scaling, etc).
If you know that all of your subviews should be redrawn (and not simply moved), then override setNeedsDisplay in your main view and do like this:
-(void) setNeedsDisplay {
[self.subviews makeObjectsPerformSelector:#selector(setNeedsDisplay)];
[super setNeedsDisplay];
}
I have tested this, and it causes all subviews to be redrawn as well. Please note that you will earn efficiency karma points if you somehow filter your subviews and make sure you only send that to subviews which actually need redrawn... and even more if you can figure out how not to need to redraw them. :-)
I want to draw a custom focus ring for my NSTextView subclass (which doesn't have a focus ring by default). I managed to implement it by overriding the parent NSScrollView drawRect and adding this code:
- (void)drawRect:(NSRect)dirtyRect {
if (focused) {
NSSetFocusRingStyle(NSFocusRingOnly);
NSRectFill(dirtyRect);
}
[super drawRect:dirtyRect];
}
However, I want to draw my own, custom focus ring. I have searched and searched for examples of this, and tried messing around and writing it myself, to no avail. The biggest issue I have is the fact that it will get cropped to the NSScrollView/NSTextView frame, no matter how I do it.
Thanks.
Updating this answer for 10.7+:
Now you should override drawFocusRingMask to render (simply drawing a shape; the system will take care of color/style), and override focusRingMaskBounds to hint at its boundaries. Also, call noteFocusRingMaskChanged if you change the shape in some way that the system could not figure out on its own.
(Below is the previous answer, requiring older APIs:)
In the Carbon framework there are HIThemeBeginFocus() and HIThemeEndFocus(), which allow you to cause any series of drawings (such as a rectangle or shape) to have an automatic "focused" appearance. Requires Mac OS X 10.5 or later.
This uses Core Graphics directly. To find the CG context from a drawRect: method in Cocoa, you'd do something like:
NSGraphicsContext* contextMgr = [NSGraphicsContext currentContext];
CGContextRef drawingContext = (CGContextRef)[contextMgr graphicsPort];
As far as avoiding clipping, one option is to use a parent view (such as an NSBox that has no border) to give extra padding. Perform the custom drawing at an inset location in the parent view that won't be clipped; in other words, give the illusion that the view is a bit smaller than its actual rectangle.