I am implementing printing in my document-based Cocoa application, and I'm wondering if anyone can help me out with this task.
I have to use a custom pagination scheme because the main view works in ways that normal pagination methods would not support. This works, however my view ends up being too big for the paper size most of the time. Tiling the view across multiple pages is not acceptable for my app, I would like to have my custom pagination work in the same way the NSFitPagination method works; if the view is too big for the page, it will resize the view by scaling it.
I thought I could do this by simply overriding the drawRect: method of my view and applying a transform to the current graphics context before it is drawn. However, it appears that printing mechanism calls the drawRect: method independently for each individual subview of a view that's being drawn, so applying a scale to the drawRect: of the superview doesn't work.
Any thoughts?
I solved this by instead of adding my view as a subview of the view to be printed, I overrode the drawRect method of the view being printed and manually set up a transformation and scaling and called drawRect on the view to print as a subview.
Related
According to the documentation for NSView's drawRect:
If your app manages content using its layer object instead, use the updateLayer method to update your layer instead of overriding this method.
I have an NSView with subviews that are provided by the framework, and they all draw using drawRect:. This framework-provided view is a subview of an NSView for which I require a layer. Because my framework-provided view is a descendant of a layer-backed view, drawRect: isn't usually called, especially in cases where the window is made active or inactive (the view needs to update to reflect its (in)active state).
Of course if I make my containing view not layer backed, updates occur when the window is made active or inactive.
Without modifying the framework into a custom fork, what's the best avenue for making sure drawRect: occurs when needed in my framework-provided view?
Thanks.
Edit 25-Aug-2018:
It looks like the trick is to set one of the views in the hierarchy to, e.g., [view setCanDrawSubviewsIntoLayer:YES, which according to the documentation uses all of the subviews’ drawRect: to add their drawing to its own layer. However this seems to work only through 10.13, and is broken in the 10.14 beta. I'll continue to look for a potential API change, unless this is a 10.14 beta bug.
Since the issue is still unresolved, it's not really answered yet.
Layer-backed views which don't override -wantsUpdateLayer to return true still draw themselves using -drawRect:. The bit of documentation you quoted is using "should" to mean "should, for best performance,". It's not required, it's just recommended.
Views don't generally redraw themselves just because the containing window has changed key or main status. You would have to mark them as needing display. Or the framework should be doing that.
I suspect the reason that it works when your view is not layer-backed is that you are marking your view as needing update. Since non-layer-backed views draw into the window's backing store using the painter model (back to front), if your view redraws itself then any subviews will have to redraw themselves on top of your view's drawing.
If the framework's views need to redraw when the window's key/main status changes, then they should be observing the relevant notifications and setting themselves as needing display. If they're not doing that, it's a framework bug. You can work around it by marking them as needing display yourself.
I got a problem with Cocoa and its View redraw hierarchy.
I'm currently testing displaying (audio) levels in a meter style control and I'm using the MeteringView class from MatrixMixerTest example project from apple. This class is drawing the meter and only drawing the difference what got changed which looks like a very efficient class.
My project is splitted into 2 splitviews, in some are NSCollectionViews (Scrollview, Clipview) and in others are only static views. If I add the meter to those "static" views they work fine when these views call setNeedsDisplay:YES. If a meter is added to the view of a CollectionView Item it gets rendered, but loosing its drawn "old level" parts and its corners/background. I think this happens because the CollectionView item gets also called to be redrawn (which has a background image) and everything is gone. It is drawing some parts whats currently changing (the drawing works).
Is there a way to prevent the Item itself to be redrawn? Or, I dont know why it is not happening in those static views, because those views also have background images but do not draw over the meter.
Are there some tricks or whats different in a CollectionView than in a "normal" view?
EDIT: After reading about isOpaque (MeteringView isOpaque = YES) means it should not call the parent views drawRect if set to yes. Well that works for the static views, those MeteringViews do not call parents drawRect, but those in a CollectionView do however. I dont know why.
EDIT 2: I gave this topic another title, because isOpaque=YES in MeteringView is not stopping calling the parents drawRect in a CollectionView, in a normal view it is working. Are there some things to know about? I have to stop redrawing the CollectionView Item because thats the problem.
Thanks in advance guys
Benjamin
isOpaque is just hint to the system. It does not prevent other views from drawing their contents, it only means that it can sometimes skip making other views update their contents.
If your view is opaque, it should draw itself as opaque and completely fill its bounds.
I have a NSView as the documentView for a NSScrollView. I also have a NSImageView as a subview of the NSView. The image dynamically changes size so the scroll bars become active/inactive at various times. Once the image has changed, I'd like to scroll to a certain point on the image. From within the NSView's drawRect: method, I call
[[myScrollView contentView] scrollToPoint: myPoint];
The scroll bars update and the image appears as I'd like, but as soon as the image is scrolled, a double image appears or parts of the image get cut off. Any help would be appreciated. Thank you.
Sounds like you might want to turn off the "Copy On Scroll" behavior option of the NSScrollView either in Interface Builder or programmatically.
From Scroll View Programming Guide for Mac OS X: How Scrolling Works:
The NSClipView class provides low-level scrolling support through the
scrollToPoint: method. This method translates the origin of the
content view’s bounds rectangle and optimizes redisplay by copying as
much of the rendered document view as remains visible, only asking the
document view to draw newly exposed regions. This usually improves
scrolling performance but may not always be appropriate. You can turn
this behavior off using the NSClipView method setCopiesOnScroll:
passing NO as the parameter. If you do leave copy-on-scroll active, be
sure to scroll the document view programmatically using the NSView
method scrollPoint: method rather than translateOriginToPoint:.
I have an application with an NSTableView in a window. I want to use a CALayer as the background for the entire window, and the table view. In all my my experiments so far, the CALayer always draws over the NSTableView, which is not the effect I'm looking for. Is there a way to make this work, or am I simply out of luck due to the nature of layer-hosting views vs NSViews?
My test setup is a window with the usual NSScrollView/NSTableView combo, and a sibling NSView behind it in the view order. The NSView is set to be layer-hosting with my custom layer within it (just a layer with a backgroundColor set). I've experimented with setting the window's content view to be layer-backed, as well as the table view itself, as well as wrapping the NSScrollView in a layer-backed NSView. The result is always the same.
Thanks for any insight you might be able to provide.
It is simple. all overlapping views or layers should be layer backing or layer hosting for correct ordering.
you can set [tableview setWantsLayer:YES]
or simply check it in the layers tab when editing the interface.
You see this in iPhone apps like Gilt. The user scrolls a view, and a subview apparently "sticks" to one edges as the rest of the scrollView slides underneath. That is, there is a text box (or whatever) in the scrollView, that as the scrollView hits the top of the view, then "sticks" there as the rest of the view continues to slide.
So, there are several issues. First, one can determine via "scrollViewDidScroll:" (during normal scrolling) when the view of interest is passing (or re-appearing). There is a fair amount of granularity here - the differences between delegate calls can be a hundred of points or more. That said, when you see the view approach the top of the scrollView, you turn on a second copy of the view statically displayed under the scrollView top. I have not coded this, but it seems like it will lack a real "stick" look - the view will first disappear then reappear.
Second, if one does a setContentOffset:animated, one does not get the delegate messages (Gilt does not do this). So, how do you get the callbacks in this case? Do you use KVO on "scroll.layer.presentationLayer.bounds" ?
Well, I found one way to do this. When the user scrolls by flicking and dragging, the UIScrollView gives its delegate a "scrollViewDidScroll:" message. You can look then to see if the scroller has moved the content to where you need to take some action.
When "sticking" the view, remove it from the scrollView, and then add it to the scrollView's superview (with an origin of 0,0). When unsticking, do the converse.
If you use the UIScrollView setContentOffset:animated:, it gets trickier. What I did was to subclass UIScrollView, use a flag to specify it was setContentOffset moving the offset, then start a fast running timer to monitor contentOffset.
I put the method that handles the math and sticking/unsticking the child view into this subclass. It looks pretty good.
Gilt uses a table view to accomplish this. Specifically, in the table view's delegate, these two methods:
– tableView:viewForHeaderInSection:
and – tableView:heightForHeaderInSection: