Does NSView have anything analogous to UIView's setNeedsLayout/layoutSubviews methods? - cocoa

Do I put such things into the display method? Or is there something analogous?

As of OSX 10.7:
- (void)layout is equivalent to layoutSubviews
There is now an identical setNeedsLayout.
Override this method if your custom view needs to perform custom layout not expressible using the constraint-based layout system. In this case you are responsible for calling setNeedsLayout: when something that impacts your custom layout changes.
You may not invalidate any constraints as part of your layout phase, nor invalidate the layout of your superview or views outside of your view hierarchy. You also may not invoke a drawing pass as part of layout.
You must call [super layout] as part of your implementation.

Analogous to layoutSubviews is the resizeSubviewsWithOldSize: method of NSView. I guess, analogous to setNeedsLayout would be calling resizeSubviewsWithOldSize:[self frame].size directly.

resizeSubviewsWithOldSize: seems like it does nothing. Never gets called at all for me. Maybe it's because I use autolayout and the documentation says it's related to autoresizing.
NSView's layout appears to be the same as UIView's layoutSubviews. They're both overridable if you want to do some special work to replace or in addition to autoresizing or autolayout.
Calling this on UIView
[view setNeedsLayout];
seems to be the same as this on NSView
view.needsLayout = YES;
Which begs the question, why is "setNeedsLayout" even a function name? Like, really, you decided to remove the parameter from a setter function, and keep the "set" in the title? Why not "scheduleLayout"? This would obviate the need for Stack Overflow questions like this.

Sometimes, resizeSubviewsWithOldSize doesn't get called.
Then, try overriding resizeWithOldSuperviewSize.

Related

Cocoa Programming: Adding a Rectangle to a Custom View (NSView)

Is there a simple way to add a simple rectangle to a Custom View without using a custom NSView subclass for it? Something along the lines of:
Assign an IBOutlet (let's call it colorWheelView) of NSView type to the CustomView
In my NSViewController's initWithNibName use it to change draw the rectangle:
// pseudocode
self.colorWheelView.addRectangle(myRectangle);
self.redraw()
The only way I've seen it done (on this site, and in my book Cocoa Programming for Mac OSX, pp. 241) is by making a custom class for the Custom View and modifying its drawRect method... Is this really the only way to accomplish this?
Edit: not sure why formatting is not being rendered correctly. I'm trying to fix it.
It really isn't all that hard to roll your own..
Just add an NSArray property to your NSView subclass, then in your drawRect method draw them either manually or using one of the NSRectFillList* methods provided by AppKit already.
(Beware: those take a plain C array, not an NSArray).
You wouldn't want to manually trigger the redraw from outside the view as in your sample code, though. To keep things consistent your addRectangle would trigger a redraw of the view itself e.g. by calling setNeedsDisplay:.

Call initWithCoder explicitly or ViewDidAppear equivalent in UIView?

In a UIView I have a nav button with an IBAction & method in the top-level view controller.
In the IBAction code, I flip a boolean so that when execution returns to the UIView, there's some new setup prior to drawRect: repainting the view.
If all this were in the ViewController, I could put the new setup code in something like ViewDidAppear so it executes each time the button is pressed. However, there's no such method at the UIView level. There is initWithCoder, but this only seems to be executed once (when the storyboard/nib loads).
So my question is - either, is there a way to call the initiWithCoder method explicitly from my IBAction at the VC level (I've tried [self initWithCoder:nil] but the breakpoint at the UIView level doesn't trigger) or is there a method that runs when execution returns to the UIView level, a la ViewDidAppear?
Thanks
Image of goal:
Unless you really know what you're doing (I mean really know), don't call -initWithCoder: yourself. You're meant to implement it just as you implement -drawRect: and let the system call it. If you ever find yourself calling something like this directly and you can't explain the deep technical reasons why there's no other way, then it's the wrong approach. Read and follow the documentation (not just the method's doc) to make sure you understand whatever method you're using. It'll tell you.
That said, what you're wondering is if there's a point in a view's lifecycle where you can "do something" (check a BOOL and perform some work if YES/NO) any time the view "appears". The answer is yes, and -willMoveToSuperview "can" work.
BUT
That's the "wrong" approach, IMO. The BOOL property ('draw a twiddle next time I'm asked to draw) can and probably should live in the UIView, but its state should be set in its controller since this is specific to your app. Views are supposed to be (highly) reusable; controllers are supposed to implement your app's specific logic and drive the views according to the model state and user (or system) actions.
So: when you want to enable the "draw a twiddle" operation, your view controller should set the view instance's drawTwiddle flag then probably flag the view for drawing. Your view will then have -drawRect: called at some point you shouldn't try to control and, when it does, it sees that self.drawTwiddle == YES and draws the twiddle along with whatever other drawing it does.
At that point, you might be tempted to have the view set its own drawTwiddle flag to NO since the behavior is intended to fire once. Don't do this. BEWARE: Other user actions or system events may call -drawRect: at any time so the twiddle may not actually be seen by the user (it may appear and disappear faster than is visible). 'So', the right thing to do is to make the controller (via some direct action, system event, or timer) responsible for setting and unsetting the drawTwiddle flag, then flagging the view for redisplay.
Adding
It's also unusual to put an IBOutlet or an IBAction in a UIView. Most of the time, unless you're creating some compound control whose parts aren't intended to be accessed and managed individually, your architecture is clearer (and more closely follows the spirit of the MVC design pattern) by letting the controller manage/own the outlets and actions.

How to tell what's causing drawRect to be called?

I've got my custom NSView with a bunch of custom buttons in it, the buttons are added as a subView in the NSView's drawRect method.
Now I'm finding that after pressing a button the drawRect of the parent view is repeatedly called. Sometimes it only stops when I quit the app - I know this from a simple log statement in drawRect.
Now I know there are probably bigger architectural issues in my app causing this, where do I go to begin tracking down what's causing this view to be repeatedly redrawn?
Thanks!
First of all you shouldn't be adding subviews in drawRect:.
Are you doing any custom drawing or are you just adding subviews? If you're not doing any drawing, you should not implement drawRect:.
You want to add the subviews in initWithFrame: and then you want to set the frames of the subviews in layoutSubviews based on self.bounds.
If you have any questions, feel free to ask.
EDIT: Just realized that you were asking about NSView and not UIView. I've never used NSView, but perhaps they work similarly.
EDIT 2: I read a bit about NSView and it doesn't seem to have layoutSubviews. Perhaps you should set the frames in drawRect:? I'm still pretty sure you don't want to add subviews in drawRect:.

cocoa -- What is the proper way to tell an NSWindow to redisplay its contents?

According to the NSWindow Class Reference, you should "rarely need to invoke" the NSWindow methods "display" or "setViewsNeedDisplay". So what is the usual way to redisplay the window's contents?
EDIT: I am having trouble dealing with resizing events. I just want to have everything scale proportionally. See this question. As no one seems to have any ideas for using masks to get it to happen, I want to redraw the whole thing.
Jason's comment really should be an answer:
Generally you don't need to. Instead, you invalidate whatever view needs to be invalidated for whatever reason within the window.
In addition to that comment, I'd add that you might want to explain why you feel you need to do this. While there are sometimes perfectly valid reasons to force the whole window to redraw, they are rare and you should suspect You're Doing It Wrong™.
Use this method to flag subviews for redisplay:
- setNeedsDisplay:YES

How to get notifications of NSView isHidden changes?

I am building a Cocoa desktop application. I want to know when a NSView's isHidden status has changed. So far using target/action doesn't help, and I can't find anything in NSNotification for this task. I would like to avoid overriding the setHidden method, because then I'll have to override all the NSView derived class that I am using.
UPDATE: I ended up using KVO. The path for "isHidden" is "hidden", probably because the setter is "setHidden".
You could use Key-Value Observing to observe the isHidden property of the NSView(s). When you receive a change notification from one of these views, you can check if it or one of its superviews is hidden with -isHiddenOrHasHiddenAncestor.
A word of warning: getting Key-Value Observing right is slightly tricky. I would highly recommend reading this post by Michael Ash, or using the -[NSObject gtm_addObserver:forKeyPath:selector:userInfo:options] method from the NSObject+KeyValueObserving category from the Google Toolbox for Mac.
More generally, one can override viewWillMoveToWindow: or the other related methods in NSView to tell when a view will actually be showing (i.e. it's window is in the window display list AND the view is not hidden). Thus the dependency on KVO for the 'hidden' key used above is removed, which only works if setIsHidden has been called on that view. In the override, 'window' (or [self window]) will indicate whether the view is being put into a visible view hierarchy (window is non-nil) or being taken out of it (window is nil).
I use it for example to start/stop a timer to update a control from online data periodically - when I only want to update while the control is visible.
Could you override the setter method for the hidden property so that it will trigger some custom notification within your application?

Resources