UIScrollView calls layoutSubviews() each time its scrolled - objective-c

I subclassed UIScrollView (IPhone SDK) and overrode the (void)layoutSubviews; method.
I noticed that each time the scrollView is scrolled, this method is called.
Is that the correct behaviour or do I have mistakes in my code? If it is the default behaviour, isn't this a performance killer?
Sincerely,
heinrich

It is the correct behaviour and it should be used to get a custom layout of your subviews. I have used it several times and haven't had any performance issues eaven with hundreds of items added.
A cut-out from the documentation on that topic:
Subclasses can also be containers for
other views. In this case, just
override the designated initializer,
initWithFrame:, to create a view
hierarchy. If you want to
programmatically force the layout of
subviews before drawing, send
setNeedsLayout to the view. Then when
layoutIfNeeded is invoked, the
layoutSubviews method is invoked just
before displaying. Subclasses should
override layoutSubviews to perform
any custom arrangement of subviews.

Related

Should we call ads in viewDidLoad or viewDidAppear?

We have a tab-heavy app, which has 5 tabs to use back and forth. We have iAds and admobs(as backup for countries without iAd), and we 'call' the ads in viewDidLoad. Would it make a difference to call them in viewDidAppear instead? And then remove them in viewDidDisappear or shomething not to screw up the frames etc? Would this give more impressions etc?
viewDidLoad:
viewDidLoad
Called after the controller’s view is loaded into memory.
- (void)viewDidLoad
Discussion
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView method. You usually override this method to perform additional initialization on views that were loaded from nib files.
viewDidAppear:
viewDidAppear:
Notifies the view controller that its view was added to a view hierarchy.
- (void)viewDidAppear:(BOOL)animated
Parameters
animated
If YES, the view was added to the window using an animation.
Discussion
You can override this method to perform additional tasks associated with presenting the view. If you override this method, you must call super at some point in your implementation.
Answering
So viewDidLoad is called slightly earlier than viewDidAppear: , the only difference is that when viewDidAppear: the view have been already drawn, instead in viewDidLoad the view has still to be drawn.
So answering to your questions:
Would it make a difference to call them in viewDidAppear instead?
If calling the ads is a slow operation, then you would see first the view appearing in it's color, and the ads after a few interval of time.However this has to be too slow to make a real difference.
And then remove them in viewDidDisappear or shomething not to screw up the frames etc?
It doesn't "screw up frames", that for sure.
you just need call it in viewDidLoad
Putting your ad code in viewDidAppear: (and removing it in viewDidDisappear:) will certainly give you more impressions, but unless you're a whitelisted pub, you're probably getting paid on a cost per click basis anyway (AdMob Help Center article).
In this case, instead of having the overhead of creating and destroying GADBannerView objects on tab changes, you might as well create a singleton GADBannerView that you use throughout your TabbedController (look at an example here).

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.

setNeedsDisplay and subviews

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:

NSScrollView disable autoscrolling

How do I disable the autoscroll in a NSScrollView as I'm filling it with content?
I have subclassed it in my own class but I don't find an appropriate method to override.
There's probably more than one way to go about this, but take a look at -[NSClipView constrainScrollPoint:]. It's not meant to be called directly, rather to be overridden in an NSClipView subclass. NSClipView is the class that actually does the real work of scrolling the content of an NSScrollView.
You should be able to override it to simply return the document view's current origin point when you don't want scrolling to happen. It's worth noting that this will also disable scrolling via the user-visible interface, so you should only do it while you're filling the view with content. Otherwise, return the result of a call to the superclass's implementation.