Cocoa: NSView drawRect painting over IBOutlets - cocoa

I have an NSView in IB which sits above the app window. I have a subclass of NSView (AddSource) which I assign to the NSView.
On awakeFromNib I instantiate the view:
//add a new Add Source class
addSourceView = [[AddSource alloc] initWithFrame:NSMakeRect(0.0, 959.0, 307.0, 118.0)];
[[winMain contentView] addSubview:addSourceView];
in addSourceView's drawRect method I am adding a white background to the view:
[[NSColor whiteColor] set];
NSRectFill(rect);
[self setNeedsDisplay:YES];//added this to see if it might solve the problem
In winMain's contentView I have a NSButton that when clicked slides the addSourceView onto the window:
NSRect addSourceViewFrame = [addSourceView frame];
addSourceViewFrame.origin.y = 841.0;
[[addSourceView animator] setFrame:addSourceViewFrame];
But it seems as if the app is painting over the IBOutlets I placed on the NSView in IB. If, in IB, I repoistion the NSView so that it is on screen when the app launches everything works fine, the IBOutlets are there as well as the background color.
I'm not sure why this is happening. I've done this before with no problems. I must be doing something different this time.
Thanks for any help.
*note - on the 3rd screen capture, when I say this is what the app looks like when opened, that's when I hard code the Y position of the NSView. When it is functioning correctly it should open as screen capture 1.

Most likely your buttons and custom view are siblings, i.e. they are both subviews of your window's content view. Since siblings are "Stacked" depending on the order in which they are added, when you add the view in code it is being added on top of the buttons. You should be able to fix it by explicitly specifying where the view should be positioned relative to its new siblings like so:
[[winMain contentView] addSubview:addSourceView positioned:NSWindowBelow relativeTo:nil];
which should place it below any existing subviews of your window's content view. Also, remove the setNeedsDisplay: line in drawRect, that leads to unncessary, possibly infinite, redrawing.
EDIT: OK I see what you're doing.
I would suggest creating a standalove view in the NIB by dragging a "Custom View" object into the left hand side (the vertically-aligned archived objects section) and adding your controls there, that should ensure the controls are actualy subviews of the view, then you can just create a reference to the archived view in code, and add/remove it dynamically as needed.
Honestly though, you should probably be using a sheet for these kinds of modal dialogs. Why reinvent the wheel, and make your app uglier in the process?

You added TWO AddSource views to the window. You added one in IB - this view contains your textFields and buttons that are connected to the IBOutlets and it is positioned outside the window.
Then in -awakeFromNib you create another, blank AddSource view (containing nothing) and animate it into the window.
I can't recommend highly enough the Hillegass as the best introduction to IB and the correct way to build Cocoa Apps.
Also, Assertions can be useful to make sure what you think is happening is actually what is happening.
If you are certain you added a button to your view in IB, assert it is so:-
- (void)awakeFromNib {
NSAssert( myButton, #"did i hook up the outlet?");
}
NSAssert is a macro that has zero overhead in a release build.
Calling [self setNeedsDisplay:YES] from -drawRect just causes the same -drawRect to be called again. This will give you big problems.

Related

NSButtons leave artefacts when used as subviews of a custom toolbar view

I'm placing a few buttons in a simple rectangular NSview which acts as a custom toolbar. On first render the buttons/views come out as expected, but every time a button is pressed (and sometimes with no mouse interaction at all) artefacts start appearing.
Before
After
I can eliminate the artefacts by calling a [self.toolbarView setNeedsDisplay:YES] in all the action and focus methods but this seems like a hack, is there any clean way to deal with this?
It was a beginner's problem. In the drawRect method
- (void)drawRect:(NSRect)dirtyRect
I was using the param dirtyRect for drawing an outline of my view, assuming it was the view's bounds, where in fact it was only the area around the buttons that became dirty when they were pressed. The 'artefacts' were actually my outline being drawn in the wrong place.
By correctly using the bounds of the view
NSRect drawingRect = [self bounds];
the 'artefacts' no longer appeared.
You just try to set focus ring for a buttons to 'none' in IB.

How to pass scroll events to parent NSScrollView

I need fixed-size NSTextViews inside a larger scrolling window. IB requires that the textviews be inside their own NSScrollViews, even though their min/max sizes are fixed so that they won’t actually scroll. When trackpad gestures are made within the textview frames (regardless of whether they have focus), they are captured by the textviews’ scrollviews, so nothing happens.
How do I tell the textviews’ scrollviews to pass scroll events up to the window’s main scrollview? (Or perhaps I should be asking how I tell the window’s main scrollview to handle these events itself and not pass them on to its child scrollviews.)
The IB structure is like this:
window
window’s content view
big scrollview for window (desired target for scroll events)
box
swappable content view in separate xib
scrollview for textview
textview
And, yes, the window does scroll correctly when the textviews do not have focus.
You needn't create a outlet "svActive" to track your super scrollview. Just write this sentence in scrollWheel event:
[[self nextResponder] scrollWheel:event];
this will pass the event to next responder in the responder chain.
IB does not require you have a text view inside a NSScrollView; this is just the default, because most of the time you'll want your view to scroll. Select the NSTextView and choose Layout > Unembed Objects. Note that after this point, you can no longer move or resize your view in IB. This seems to be a bug.
Here's an example of how to put two NSTextViews in a single NSScrollView.
Add two text views next to each other; put some text in them so you can see what's happening.
Select the views; choose Layout > Embed Objects In > Scroll View. This puts them in a generic NSView inside a NSScrollView.
Select the text views; choose Layout > Unembed Objects.
Turn off the springs and struts (autosizing) for each text view, so they don't resize when you shrink the scroll view.
Take note of the height of the document view (here it's 175).
Make the scroll view smaller. This also resizes the document view (NSView).
Restore the document view to its original size (I set the height back to 175).
Done! Scrolling works as you'd expect.
This is really embarrassing. After weeks of putting it off, I made a first attempt to get a subclassed NSScrollView to behave passively — and it turned out to be a no brainer.
Here’s the subclass:
h file:
#import <Cocoa/Cocoa.h>
#interface ScrollViewPassive : NSScrollView {
// This property is assigned a ref to windowController’s main scrollview.
NSScrollView *svActive;
}
#property (nonatomic, retain) NSScrollView *svActive;
#end
m file:
#import "ScrollViewPassive.h"
#implementation ScrollViewPassive
#synthesize svActive;
// Pass any gesture scrolling up to the main, active scrollview.
- (void)scrollWheel:(NSEvent *)event {
[svActive scrollWheel:event];
}
#end
There’s no need to make outlets for these passive scrollviews; I give them their refs to the main scrollview right after their xibs are assigned as content to the NSBox:
[self.boxDisplayingTextViews setContentView:self.subviewCtllr1.view];
// A textview's superview's superview is its scrollview:
((ScrollViewPassive *)[[self.subviewCtllr1.textview1 superview] superview]).svActive = self.scrollviewMain;
That’s it. Works like a charm.
I find that IB3 and Xcode4 both fight you if you try to do this directly, but you can do it indirectly. First, drag the textview out of the scrollview and delete the scrollview. You'll wind up with an orphaned textview. I don't know any way to get IB to allow you to put this into your window, but it'll be in your NIB. Now, attach an IBOutlet to it, and at runtime do a addSubview: and adjust its frame to move it into whatever scrollview you wanted it to be in.
In my experience, NIBs are a good thing, but every complex NIB I've ever worked with needed some final rearranging in code.
Based on #phaibin's answer, here's how you'd do it in Swift 4.2.
First, subclass NSScrollView and override scrollWheel:
class ScrollThingy: NSScrollView{
override func scrollWheel(with event: NSEvent) {
self.nextResponder?.scrollWheel(with: event)
}
}
Then place the ScrollThingy class (or whatever you name it) on the NSScrollView that is wrapped around your NSTextView. The parent NSScrollView will get the scroll event thereafter.

NSView overlapping

My cocoa app has a "dashboard" style layout.
When the app starts, the main window contains 6 views which display graphs.
When I click on any part of the bottom most view, I have another NSView instance popping up
as an annotation. The problem I run into is that is the pop up NSView is large enough dimension wise,
the other views in the window overlap the pop up view. Currently, I do that with:
[[self superview] addSubview:annotationView ]; where 'superview' is the window.
Im not sure why this would be the case, I have tried removing the the pop up view from the "view stack"
and making it change positions but that didnt work.
[[[self window] contentView] insertView:popupView atIndex:0];
This will insert the view at the top level, if you still can't see it you will need to add a subview to the superview of the NSWindow's contentview.
If all the views are added as subviews of [self superview] then you must make sure that they do not overlap. Cocoa doesn’t guarantee correct behaviour in case there are overlapping sibling views.
If you want a popup view, consider using a child window instead. Since it’s a different window, the popup lies in a different view hierarchy, hence you won’t have the overlapping sibling views problem.
A good example of using child windows for additional information is Matt Gemmell’s MAAttachedWindow.

NSTextField over NSOpenGLView

I have made a window with an NSOpenGLView that I am rendering openGL content into.
I want to add some buttons and text fields to the view: I can add NSTextFields and NSButtons using interface builder (or code) but they do not appear.
NSOpenGLView is documented as not being able to have sub views, so I then made my own CustomGLView by deriving directly from NSView and implementing the code to create and use a NSOpenGLContext in it. But the subviews are still not appearing :- the OpenGL context paints over them.
On Windows this problem does not exist:- Windows used to host OpenGL MUST have the WS_CLIPCHILDREN and WS_CHIPSIBLINGS styles set ensuring that any peer, or sub children (views) will not be obscured by the OpenGL surface.
How do I get subviews to display over a NSView thats drawing using OpenGL ?
You have 2 choices:
Create a window just for the text field. Add as a child window of the one hosting the OpenGL view. Major downside is you have to manage positioning it correctly if the Open GL view is moved.
Set up your view hierarchy like so:
Layer-backed view
Layer-hosting view whose layer contains an OpenGL layer
Text field
Simply call -setWantsLayer:YES on the subviews of the NSOpenGLView.
NSOpenGLView cannot have subviews according to the documentation. Even if you subclass the NSOpenGLView, that will change nothing.
What you can do is to create a NSView that will hold both the NSOpenGLView and the NSTextField. You then overlap them in the right order to make one draw atop the other.
I'm not heavily into OpenGL yet, but it's my understanding that you can accomplish the visual effect of subviews with Quartz Extreme using layer-backed views; however, those may be problematic. Since subviews are not supported directly, any solution is liable to be a hack.
Indeed, the solution in that link actually hacks a second window to appear over your OpenGL display, the second window displaying the Cocoa views you desire.
The following code (from the above link) is something I've not tested (again not being an OpenGL guy by nature -- yet), but appears like a fairly clever approach:
// This is the GL Window and view you already have
glWindow = [[GLWindow alloc] initWithContentRect:windowRect];
glView = [[[GLView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[glView translateOriginToPoint:NSMakePoint(glView.bounds.size.width/2, glView.bounds.size.height/2)];
[glWindow setContentView:glView];
// And here's your transparent UI window
uiWindow = [[TransparentWindow alloc] initWithContentRect:windowRect];
uiView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[uiView translateOriginToPoint:NSMakePoint(uiView.bounds.size.width/2, uiView.bounds.size.height/2)];
uiView.wantsLayer = YES;
[uiWindow setContentView:uiView];
[glWindow addChildWindow:uiWindow ordered:NSWindowAbove];
Again, I've not tested this, but it looks like it will get you the visual effect you desire.
The text can be rendered into a texture -- I just used this for a project, did a lot of looking for sample code, and ultimately found Apple's GLString demo code, which was an absolute trove of how-to:
http://developer.apple.com/library/mac/#samplecode/CocoaGL/Listings/GLString_m.html
I haven't tried adding buttons, but you can, of course, draw your own and comparing the positions of click events with those of your buttons...
This was my solution:
1) Create a parent NSView (let's call it parentView).
2) Add an NSOpenGLView Child to parentView.
3) Add an additional NSView Child to parentView (make sure this is after the OpenGLView within the hierarchy). You can add additional TextFields, etc. to this view.
4) In the ViewController for the parent make sure you call [parentView setWantsLayer: TRUE]; I did this within -(void) viewWillAppear
1) The NSOpenGLView can have a subview. It can have plenty even.
2) The reason some views, controls and other elements are being bullied by NSOpenGLView is due to the loading process when the Application launches. I.e If you add a slider or textfield above and into the content view of the window where the NSOpenGLView also resides, upon Application-Launch that textfield will most likely wind up beneath the NSOpenGLView.
This is an Apple Bug. And they know about it.
You can solve it quite easily even without adding a subview to NSOpenGLView...
In Interface Builder drag i.e. a CustomView into the canvas (Not the view). And set it the way you want it with sliders, text and what not. Then create an outlet (Call it i.e topView) in your view controller. Then somewhere in your code... Perhaps (applicationDidFinishLaunching) add this line...
[_window.contentView addSubview:_topView];
(Do your positioning & layout)
This will do the exact same thing as if you had dragged it into the contentView yourself inside IB. Only it will draw the darn thing in the correct Z position.
You loose IB's constraints this way and have to it manually
One could also just subclass and CAOpenGLLayer and use that as a backing layer inside of a regular NSView. There too it is drawn correctly...
Here is Apple's way of wanting to do that. CALayers are a Godsend ;)
Enter following String ** NSOpenGLLayer** in search and hit enter to get to where it is...
NSOpenGLLayer
Hope this helps....

Cocoa: [statusItem setView:myView] makes a white bar menu item no matter what

In my small app for Mac OS X I display some info in system menubar. I use
statusItem = [
[[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength]
retain
];
It works very nice and I can change the text with
[statusItem setTitle:[NSString stringWithString:#"Woo-hoo"]];
But it uses the default menu font which is too big for my relatively unimportant info. So I decided to reimplement it with a custom view. I created a view in Interface Builder.
Unfortunately, however, when I set it as a view for my menu item with
[statusItem setView:myView];
it just displays a white bar in the menu instead of my thing. I tried to
[statusItem
drawStatusBarBackgroundInRect:[myView frame]
withHighlight:NO];
with no success.
In trying to figure out whether a problem is with the view itself or with the way I assign it to the menubar, I created a window and did
[myTestWindow setContentView:myView];
This one worked seamlessly. This makes me think my view is OK :-)
So, what else can I try to make the menu item display my own view?
Thanks!
It happened to be some weird side-effects of window-view autosizing setup in Interface Builder (let’s call them size-effects). In the Inspector you can setup how subviews get resized upon superview sizing. And so it was somehow broken in my case, such that when window gets small enough (menuitem-high), my elements just got drawn outside of the window’s frame.
I re-configured the sizing in IB, eliminating all the automatics I don’t need, and now it works perfectly: the view from IB gets displayed inside a menu item.
What is the height of the frame of the view? Maybe your view is taller than the menubar and you are drawing outside of it. The current menubar is 22 pixels, but you should ask the systemStatusBar for it's thickness, just in case it ever changes.
Try drawing a frame around your view to see if you are getting anything.
[[NSColor blueColor] set];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:self.bounds];
[path setLineWidth:4.0f];
[path stroke];
If you get just an 'L' shape (the bottom left corner) of blue then the view is too large. If you get a rectangle but still no text then you may not be drawing the text inside the view, look at the coordinates you are drawing the text at (and review View Geometry). Putting the view in a window may have worked because it is larger.
For an example of using text in a status menu view take a look at Matt Gemmell's NSStatusItemTest project.
EDIT:
Sorry, somehow I missed where you said you created the view in IB. I did a quick test and I can see the white box you mentioned.
The docs for NSStatusItem's setView: states
The custom view is responsible for
drawing itself and providing its own
behaviors, such as processing mouse
clicks and sending action messages.
And status item views go into a special (apple private) window called NSStatusBarWindow that may have different internal behavior than normal windows and certainly seems to not support views from IB.
So yes, I think you need to create a custom NSView subclass and do your own drawing in drawrect:.

Resources