setNeedsDisplay and subviews - objective-c

From my understanding, setNeedsDisplay only affects the view it's called on. Is there a simple way to say "update this view and all its subviews, recursively?"
In response to the comments, here's my situation: I've got a custom view
#interface ContainerView : UIView
this view does not implement drawRect. In my xib there's an instance (called container) of the ContainerView which has some (custom) subviews added to it. When in the code I say
[container setNeedsDisplay]
I expect these subviews to update. Where am I wrong?

Ok, UIView draws itself when its first displayed. CALayers do not. Calling setNeedsDisplay on a UIView marks it as dirty, this automatically redraws all SubViews as well (calling drawrect on all subviews). Calling setNeedsDisplay on a CALayer doesn't have the same effect, it wont redraw sublayers. Hope this helps.
Regards
Ref
iOS 7 Programming Pushing the Limits By Rob Napier, Mugunth Kumar

UIView Class Reference
The UIView class defines a rectangular area on the screen and the interfaces for managing the content in that area. At runtime, a view object handles the rendering of any content in its area and also handles any interactions with that content
setNeedsDisplay
Marks the receiver’s entire bounds rectangle as needing to be redrawn.
Note: If your view is backed by a CAEAGLLayer object, this method has no effect. It is intended for use only with views that use native drawing technologies (such as UIKit and Core Graphics) to render their content.
The subviews are inside the bounds of the view, so the view will ask it's subview what to display.
Have you try and encounter some case that go agains this definition?
If you are implementing your own UIView subclass you need to handle all the display yourself in drawRect:

Related

setNeedsDisplay does not trigger drawRect in subviews as expected

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. :-)

UIView subviews's layout update

In the viewWillAppear delegate method, I'm setting subview's frames with rects ( location and size).
Now, There is one subview whose content will grow with user actions.
So, I need a callback in that superview controller ( The One I am talking on first sentence) , to update the layout. In this callback, I can find out how much the size increased, and then set other subviews frame rects too.
It can be done through calling setNeedsLayout method on the view, but it requires layoutSubviews to be overridden on the UIView. Please note that it is not a delegate method, I need to have a custom view, Which I don't want to do,
Is there any delegate method for me to update the layout in the view's controller ?
UPDATE:
This need to be support on iOS 4.2
There are two UIViewController methods of possible relevance:
-viewWillLayoutSubviews
-viewDidLayoutSubviews
You can probably guess when they're called.

drawRect and addSubview: custom drawing affects which views?

If I have a custom subclass of UIView that implements drawRect and controller methods use addSubview to create a view hierarchy in this custom view, how does drawRect interact with these subviews? Does it recreate the entire subclass's view hierarchy from scratch and remove any existing subviews? Or does it ignore subviews and only redraw a particular view/subview?
Would it be acceptable to programmatically add and remove subviews within drawRect?
drawRect is meant to be only for drawing your content in the view.
Whether it draws the entire view or part of it: It depends on your implementation. If you want to do any optimization is a good idea to check when your view calls drawRect and adjust the code accordingly (maybe you want to update only one part of the view, maybe you don't want to draw all the times, etc). It depends on your needs
I don't think is a good idea to add/remove subviews within drawRect because this method will be called in several situations and I dare to say that is NOT what you want :)
Instead, you could try something like this:
[myView addSubview:aSubview];
[myView setNeedsDisplay];
//or calculate the needed display rect by yourself and then
[myView setNeedsDisplayInRect:aRect];
-drawRect: doesn't interact with subviews. It draws whatever the view it's sent to wants to draw in the rect it's given.
Would it be acceptable to programmatically add and remove subviews within drawRect?
NO. -drawRect: is for drawing, not for manipulating the view hierarchy.

UIScrollView bounds have not yet resized in "viewDidLoad"

I have a UIViewController subclass whose view is configured in a NIB file. The view has a UIScrollView subview.
The UIScrollView takes up almost its entire NIB file but it's superview is added as a subview to a much smaller view (configured in a different NIB) - e.g. the UIScrollView is 80% the height of it's own NIB but ends up only being 10% the height of the application's window.
When I call [scrollView bounds].size.height in the viewController's viewDidLoad method, I get the height of the scrollView relative to it's own NIB, rather than the height it ends up resizing to as dictated by its superviews (e.g. 80% of the window's height rather than 10%).
If I call [scrollView bounds].size.height later on (e.g. to handle a rotation event), I get the correct value.
How can I get the correctly re-sized value initially?
Have you tried dealing with it in viewWillAppear: or viewDidAppear?
Assuming I'm following, there must be some point after the view controller is loaded at which you grab the .view and insert it into the other subview? If so then the bounds will be adjusted then. You can't get them in viewDidLoad because the view has no way of knowing what you'll do with it in the future. Even if your call to insert it as a subview is the very first thing you do after loading the view controller, it's the view accessor that'll cause the view to load and hence the viewDidLoad, all before you've actually put the view anywhere.
Since bounds has a getter and a setter, it should be key-value coding compliant, in which case you can use viewDidLoad to register an observer on bounds and do whatever it is you need to do when the bounds change.

"refresh" view on device rotation

I have my view set up in viewDidLoad. All the different frames and such of the subviews have been defined relative to self.view. Therefore it doesn't matter what size self.view is the subviews will always shrink or expand to fit (as it were).
So when I rotate my device I want the view to rotate (easy enough with shouldAutoRotateToInterfaceOrientation:...) but the subviews stay the same shape.
Calling [self viewDidLoad]; makes all the elements fit, but puts a new layer on top of the previous layout (which is obvious... but i'm just saying to explain what I mean).
Is there any way to refresh the frames of the subviews or something? I don't know what other people do to be honest. Am I going to have to put ALL of my views into the .h file as properties and do everything manually on didRotate...?
You have three options:
If autoresizing masks are good enough to position your views, assign the correct autoresizing mask to each subview when you create them.
If autoresizing masks are not sufficient, override willAnimateRotationToInterfaceOrientation:duration: and reposition your subviews in that method. I would create a custom method that takes the orientation as a parameter and is responsible for laying out all subviews. You can then call this method from willAnimateRotationToInterfaceOrientation:duration: and from viewDidLoad.
You could also create a custom UIView subclass and make your view controller's view an instance of this class. Then override layoutSubviews to position all subviews depending on the view's size. This approach implies that your custom view manages its subviews instead of the view controller.