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.
Related
I am trying to create a custom NSView that hosts a CALayer hierarchy to perform efficient display. This NSView is then embedded within a NSTableCellView that is displayed by a View-Based NSOutlineView.
The problem is that whenever I expand or collapse an item, all rows are being moved, but the layer's content remains displayed at the position it was before changing the outline.
Scrolling the NSOutlineView seems to refresh the layers and they resync with their rows at that point.
I have debugged this behavior using Instruments and it seems that the scrolling provokes a layout operation which updates the layers with a setPosition: call that should have occured when expanding or collapsing items.
Here is some sample code for a simple layer hosting NSView subclass.
#interface TestView : NSView
#end
#implementation TestView
- (instancetype)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
CAShapeLayer* layer = [CAShapeLayer layer];
layer.bounds = self.bounds;
layer.position = CGPointMake(NSMidX(self.bounds), NSMidY(self.bounds));
layer.path = [NSBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
layer.fillColor = [NSColor redColor].CGColor;
layer.delegate = self;
self.layer = layer;
self.wantsLayer = YES;
return self;
}
#end
I have tried a lot of potential solutions to this problem but I couldn't find any interesting method that gets called on the NSView instance that could be overriden to call [self.layer setNeedsDisplay] or [self.layer setNeedsLayout]. I also tried various setters on the CALayer itself such as :
layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
layer.needsDisplayOnBoundsChange = YES;
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
Can anyone help me figure out how to make this layer display properly inside a NSOutlineView?
I ended up answering my question. The problem wasn't in the way my TestView was implemented. I simply missed one of the steps for enabling CoreAnimation support within the application. The relevant reference is within the Core Animation Programming Guide.
Basically, in iOS Core Animation and layer-backing is always enabled by default. On OS X, it has to be enabled this way :
Link against the QuartzCore framework
Enable layer support for one or more of your NSView objects by doing one of the following
In your nib files, use the View Effects inspector to enable layer support for your views. The inspector displays checkboxes for the selected view and its subviews. It is recommended that you enable layer support in the content view of your window whenever possible
For views you create programmatically, call the view’s setWantsLayer: method and pass a value of YES to indicate that the view should use layers.
Once I enable layer support on any of the NSOutlineView's parents, the various glitches are solved.
It is difficult to read the NSOutlineView reference documents and find the information about cell reuse that is likely giving you fits here.
You may have looked at outlineViewItemDidCollapse: but it's kind of a useless for our issue, because it doesn't have a pointer to an NSView, and that's because it's older than view-based outline views.
Perhaps the one helpful mention, buried within the NSOutlineViewDelegate protocol, down in the section on view-based NSOutlineView methods, there is a single mention within outlineView:didRemoveRowView:forRow: that:
The removed rowView may be reused by the table, so any additionally inserted views should be removed at this point.
In other words, when you call the outline view's makeViewWithIdentifier:owner:, for a cellView or rowView with a particular ID you often get a recycled view. Especially often because of collapse. Incidentally, that method is from the NSTableView superclass, and in that reference, there's also this comment:
This method may also return a reused view with the same identifier that is no longer available on screen. If a view with the specified identifier can’t be instantiated from the nib file or found in the reuse queue, this method returns nil.
So you have the option of altering the view hierarchy or niling properties in didRemoveRowView:forRow. However, buried within a third cocoa reference, that for NSView, there is within the commentary on prepareForReuse, this comment:
This method offers a way to reset a view to some initial state so that it can be reused. For example, the NSTableView class uses it to prepare views for reuse and thereby avoid the expense of creating new views as they scroll into view. If you implement a view-reuse system in your own code, you can call this method from your own code prior to reusing them.
So, TL;DR, you need to implement prepareForReuse.
The pertinent references are (mostly) the superclasses of both NSOutlineView and NSTableCellView.
And, FWIW, there was a similar question here, where the questioner seems to indicate things are even worse than I think, in that NSOutlineView is more creative behind the scenes than NSTableView.
In my own work with outline views and embedded NSTextViews, I've seen wildly terrible rendering hiccups relating to expand/collapse/scroll that I seem to have managed in just the NSOutlineViewDelegate methods. On iOS they did everyone the favor of renaming makeViewWithIdentifier to the more explicit dequeueReusableCellViewWithIdentifier.
You shouldn't have to enable layer backing for any of the ancestor views (like the outline view).
In my experience, the layer immediately assigned to a view (as opposed to sublayers) doesn't need its bounds, position, or autoresizing mask to be set. It is automatically made to track the bounds of the view. In fact, I would avoid setting those properties, just in case that breaks the automatic synchronization with the view's bounds rect.
So, the question is: how are you arranging for the view to move or resize with its superview? Are you using auto layout? If so, did you turn off its translatesAutoresizingMaskIntoConstraints? If yes to both, what constraints are you setting on the view? If no to either, how did you position the view within its superview? What frame did you set? Also, is the superview configured to autoresize its subviews (probably yes, since that's the default)? What is your view's autoresizingMask?
You could also override -setFrameOrigin: and -setFrameSize: in your custom view class and call through to super. Also, add logging to show when that's happening and what the new frame rect is. Is your view being moved as you expect when you expand or collapse rows?
According to documentation on NSSplitViewControllers, the associated NSSplitView uses the NSSplitViewController as its delegate. Specifically in the documentation, "The split view controller serves as the delegate of its split view object (the object that manages the dividers). If you override a split view delegate method, your override must call super."
I have implemented an NSSplitViewController in interface builder and gave it a class. However, none of the splitView delegates are ever invoked. Additionally, if I just do something like spit who the splitView delegate [ NSLog (#"%#", self.splitView.delegate); ], the result is "null". If, however, I assign the delegate either in IB itself (by dragging the delegate outlet to the NSSplitViewController) or inside code ([self.splitView setDelegate:self];), I get the following error:
An uncaught exception was raised
SplitViewController's splitView is unable to use autolayout because the SplitViewController overrides an incompatible delegate method.
I'm completely flummoxed.
If the delegate of a split view implements one of the following methods, it becomes incompatible with auto layout.
splitView:constrainMinCoordinate:ofSubviewAt:
splitView:constrainMaxCoordinate:ofSubviewAt:
splitView:resizeSubviewsWithOldSize:
splitView:shouldAdjustSizeOfSubview:
https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#10_8AutoLayout
And because NSSplitViewController requires the use of auto layout (mentioned in the documentation), these methods are incompatible with NSSplitViewController and shouldn't be implemented in a subclass.
Extending on Taylor's answer.
In macOS 10.8 Apple made improvements to NSSplitview, which includes respecting constraints of the subviews. Apple also introduced holding priority for subviews of NSSplitview. Holding Priority is the priority of holding the size of the subview, so while resizing the splitview, subview with the lowest priority will resize first.
So to control the resizing behavior you will have to tune the holding priority of SplitView Items. This can also be done in storyboard directly
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'd love to see a detailed explanation on how to manage views programmatically. I'll provide an overview of how I'm doing it now and would like either comments on how my approach sucks or just an overview of how to do it cleanly and properly.
Basically, in my app's main view controller's loadView method, I first create a root view and set self.view to it. When I want to attach a controller, say, the first one that displays, I call this method:
-(void) attachViewForController:(UIViewController*)controller
{
[self.mRootView addSubview:controller.view];
[controller viewWillAppear:NO];
}
Notice that I explicitly call viewWillAppear (I believe it wasn't automatically calling it), where I manually perform any animations to bring the view in (sliding in, fading in, etc). Is there anything wrong or strange with this approach here?
Now, when I want to leave this view and switch to another, I call a method to setup the switch:
-(void) setControllerSwitch:(UIViewController*)outgoingController
incomingController:(UIViewController*)incomingController
delay:(float)delay;
{
self.mOutgoingController = outgoingController;
self.mIncomingController = incomingController;
self.mSwitchControllerTimer = [NSTimer scheduledTimerWithTimeInterval:delay target:self selector:#selector(switchControllerCallback) userInfo:nil repeats:NO];
}
At this moment in time, I've begun the exit animations for the outgoing view, and this method records the controllers and schedules a method that will perform the actual switch at the moment the outgoing view is done animating. Like this:
-(void) switchControllerCallback
{
self.mSwitchControllerTimer = nil;
// remove outgoing view
[mOutgoingController.view removeFromSuperview];
// add incoming view
[self attachViewForController:mIncomingController];
}
Is this a decent way to manage things? A few points:
I know I could probably instead setup a callback to trigger when the outgoing controllers animations end, but chose to just do it via an explicit delay param to leave room to allow me to cross fade views. However, I think calling setControllerSwitch early may in fact not allow crossfading because it would junk the old controller early and make it chop off its animation.
As mentioned earlier, I'm curious to know if explicitly calling viewWillAppear is a no no and there is a more appropriate way to manage view flow.
Copied from Apple developer documentation but it help me lot to understand about views and maneging multiple views.
Tips for Using Views Effectively
Custom views are useful for situations where you need to draw something the standard system views do not provide, but it is your responsibility to ensure that the performance of your views is good enough. UIKit does everything it can to optimize view-related behaviors and help you achieve good performance in your custom views. However, you can help UIKit in this aspect by considering the following tips.
Views Do Not Always Have a Corresponding View Controller
There is rarely a one-to-one relationship between individual views and view controllers in your application. The job of a view controller is to manage a view hierarchy, which often consists of more than one view used to implement some self-contained feature. For iPhone applications, each view hierarchy typically fills the entire screen, although for iPad applications a view hierarchy may fill only part of the screen.
As you design your application’s user interface, it is important to consider the role that view controllers will play. View controllers provide a lot of important behaviors, such as coordinating the presentation of views on the screen, coordinating the removal of those views from the screen, releasing memory in response to low-memory warnings, and rotating views in response to interface orientation changes. Circumventing these behaviors could cause your application to behave incorrectly or in unexpected ways.
For more information view controllers and their role in applications, see View Controller Programming Guide for iOS.
Minimize Custom Drawing
Although custom drawing is necessary at times, it is also something you should avoid whenever possible. The only time you should truly do any custom drawing is when the existing system view classes do not provide the appearance or capabilities that you need. Any time your content can be assembled with a combination of existing views, your best bet is to combine those view objects into a custom view hierarchy.
Take Advantage of Content Modes
Content modes minimize the amount of time spent redrawing your views. By default, views use the UIViewContentModeScaleToFill content mode, which scales the view’s existing contents to fit the view’s frame rectangle. You can change this mode as needed to adjust your content differently, but you should avoid using the UIViewContentModeRedraw content mode if you can. Regardless of which content mode is in effect, you can always force your view to redraw its contents by calling setNeedsDisplay or setNeedsDisplayInRect:.
Declare Views as Opaque Whenever Possible
UIKit uses the opaque property of each view to determine whether the view can optimize compositing operations. Setting the value of this property to YES for a custom view tells UIKit that it does not need to render any content behind your view. Less rendering can lead to increased performance for your drawing code and is generally encouraged. Of course, if you set the opaque property to YES, your view must fills its bounds rectangle completely with fully opaque content.
Adjust Your View’s Drawing Behavior When Scrolling
Scrolling can incur numerous view updates in a short amount of time. If your view’s drawing code is not tuned appropriately, scrolling performance for your view could be sluggish. Rather than trying to ensure that your view’s content is pristine at all times, consider changing your view’s behavior when a scrolling operation begins. For example, you can reduce the quality of your rendered content temporarily or change the content mode while a scroll is in progress. When scrolling stops, you can then return your view to its previous state and update the contents as needed.
Do Not Customize Controls by Embedding Subviews
Although it is technically possible to add subviews to the standard system controls—objects that inherit from UIControl—you should never customize them in this way. Controls that support customizations do so through explicit and well-documented interfaces in the control class itself. For example, the UIButton class contains methods for setting the title and background images for the button. Using the defined customization points means that your code will always work correctly. Circumventing these methods, by embedding a custom image view or label inside the button, might cause your application to behave incorrectly now or at some point in the future if the button’s implementation changes.
I subclassed UIScrollView (IPhone SDK) and overrode the (void)layoutSubviews; method.
I noticed that each time the scrollView is scrolled, this method is called.
Is that the correct behaviour or do I have mistakes in my code? If it is the default behaviour, isn't this a performance killer?
Sincerely,
heinrich
It is the correct behaviour and it should be used to get a custom layout of your subviews. I have used it several times and haven't had any performance issues eaven with hundreds of items added.
A cut-out from the documentation on that topic:
Subclasses can also be containers for
other views. In this case, just
override the designated initializer,
initWithFrame:, to create a view
hierarchy. If you want to
programmatically force the layout of
subviews before drawing, send
setNeedsLayout to the view. Then when
layoutIfNeeded is invoked, the
layoutSubviews method is invoked just
before displaying. Subclasses should
override layoutSubviews to perform
any custom arrangement of subviews.