Call initWithCoder explicitly or ViewDidAppear equivalent in UIView? - xcode

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.

Related

NSWindow display and setViewsNeedDisplay

In NSWindow class I found, among others, two methods: display and setViewsNeedDisplay:. But I don't know the difference between these two methods. Although documentation says: "You rarely need to invoke this method", I need to call one of these to update window's contentView. The problem that the I don't know which method to call. Maybe somebody can tell me difference between those 2 methods?
Thanks.
P.S.contentView of window is my custom view.
For both windows and views, display method redraws the object immediately, and setViewsNeedDisplay:/setNeedsDisplay: set a flag that redraw is needed while the actual redraw will happen later. Repeatedly displaying a view is expensive, repeatedly marking it for display is very cheap.
Most of the time you need to call setNeedsDisplay: on the view you want to be redrawn. Or even setNeedsDisplayInRect: to mark only a part of it, not the whole view. So if all you need is contentView to be redrawn, call [[window contentView] setNeedsDisplay:YES] and that will be it.
In rare cases, for example, before invoking a blocking API call or displaying a modal alert, you will have to call display on the view instead, otherwise the call will block for a long time before redraw happens.
In even more rare cases, you will have to call display on the window, for example, if you tinkered with areas outside content view, like title and borders.
And you almost never need -[NSWindow setViewsNeedDisplay:]. I don't know an example when one needs it.

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

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.

Animating setHidden: on NSView via Cocoa bindings

I'm currently putting the final touches on a project.
A lot (if not all) of the UI logic currently relies on Cocoa Bindings.
Some of the user interface elements (labels, buttons, etc.) have their "Hidden" bindings defined. When certain events are triggered, these elements visibility is toggled.
I'm trying to animate the visibility change (by animating the opacity and maybe even the scale). This could easily be accomplished in a number of ways, either by setting the relevant layer properties, adding the animations to the layer, etc. However, since I'm trying to totally rely on the bindings behavior I "can't" really do this directly.
I tried an implementation using Layer actions, by defining actions for the keys kCAOnOrderIn and kCAOnOrderOut on the relevant elements, but it really didn't work, as the setHidden: is most likely being triggered on the NSView instead of the CALayer -- which makes sense.
So, my question is: how would you animate setHidden: on a NSView, when setHidden: is being invoked by the Cocoa Bindings.
Thank you.
This will fade out an NSView...
[[someView animator] setAlphaValue:0.0f];
Animating setHidden will have no visual effect since it's either on or off. If you want to animate visibility, use setAlpha (or setOpacity on the layer) instead. These take a value between 0.0 and 1.0. If you need the hidden flag to get set for the sake of state information, call -performSelector:withObject:afterDelay passing it a selector that sets the hidden value to whatever you need it to be after the animation has completed. Alternatively you can set up a delegate for explicit animation to be called back when the animation finishes and call setHidden then.
I would suggest taking a look at NSViewAnimation. It takes any NSView and can animate the frame, size or visibility.

How to make a view controller first responder for an NSView in Cocoa

I'm trying to implement a view controller for a custom NSOpenGLView based view (this is Cocoa, not Cocoa Touch).
The view is contained within a NIB loaded window but it does not have its own NIB. In fact the window contains multiple instances of the view.
I want to route mouse events to the controller instead of to the view. I would like for this to happen as soon as the user clicks within the corresponding view.
So how can this be done ?
I've tried having the view's becomeFirstResponder method call makeFirstResponder with the controller as argument. However that doesn't seem to work, the view still receives the mouse events instead of the controller if NSView::becomeFirstResponder returns YES. If it returns NO then neither of my classes receive the mouse events.
Of course I could implement the mouse event handling methods in the view and explicitly forward them to the controller but it seems like there should be a better way to handle this.
For general "first responder" status, I recommend Charles Parnot's MTViewController, an NSViewController subclass that uses KVO to make certain the controller is in the responder chain with no extra effort on your part.
However, in your case, you want mouse events too. There's really no way around this - your view will need to translate mouse events into controller interactions.

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