when reading objc.io's 5th issue on UICollectionView + UIKit Dynamics , the 2nd section talked about 'Tiling your Dynamic Behaviors for Performance (related source code)', what confuse me is prepareLayout method is continuously called, but -shouldInvalidateLayoutForBoundsChange: still returns NO, and nowhere called invalidateLayout. shouldn't it just called once ?
I came across a similar issue myself when testing an iOS7 UICollectionViewFlowLayout on an iOS6 device. On an iOS7 device prepareLayout is only called once, but on an iOS6 device it is called when layoutSubviews is trigged by it's parent UIScrollView.
To fix this issue I changed my UICollectionViewFlowLayout class to UICollectionViewLayout. There seems to be a layout issues with UICollectionViewFlowLayout in this case.
Related
I am using a custom header cell, and while it works correctly in ios8, when I try it on an ios9 device the table calls dequeueReusableHeaderFooterViewWithIdentifier but it doesn't call the initWithFrame (or any init function that I tried) function, and so I just get a blank space for a header file.
Is there a change in how dequeueReusableHeaderFooterViewWithIdentifier works in ios9 that I should be awear of?
Thanks
I had the same problem with a subclass of UITableViewHeaderFooterView. Changing from initWithFrame to initWithReuseIdentifier solved the problem for me.
I could observe the following different behavior under iOS 8 and 9:
Under iOS 8
initWithReuseIdentifier: gets called first then
initWithFrame:
whereas under iOS 9
only
initWithReuseIdentifier:
gets called
If I want to initialize views programmatically, where in the viewcontroller lifecycle should this happen?
The initial intuition is loadView. However, here, we don't yet have the frame of the view itself (necessary for calculating the sizes/positions of the views). Ditto for viewDidLoad.
Next intuition is viewWillAppear- here we DO (finally) have a guarantee of the frame of the view. However, this has potential to be called many times throughout the vc lifecycle. Ditto for viewDidAppear, etc...
Finally, I found viewWillLayoutSubviews. This works for the initialization of most static layouts- however, whenever any view moves this gets called again (same problem as viewWillAppear).
I've seen recommendations to init the views in loadView and set their frames in viewWillLayoutSubviews (since setting frames should be idempotent, who cares if it gets called a couple times). But then why does apple so strongly encourage initWithFrame: as the standard initialization method of UIViews (https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/CreatingViews/CreatingViews.html)?
Would it be crazy to subclass all my UIViewControllers to have an initWithViewFrame: method? That way I can pass in a frame, manually set it immediately in loadView and be done with it? Or is it better to have a viewHasBeenFormatted flag in viewWillAppear that, if not set, calls the formatting of views and then sets it?
Or is this just apple's way of saying "use interface builder or you're screwed"?
Any help is appreciated!
edit- accidentally wrote loadView where I meant viewWillAppear (in final paragraph)
update- I guess I've come to terms with the fact that there is no place where
The frame is confidently known
The code will only be run once (on setup)
Looks like you're expected to initWithFrame: all your views in viewDidLoad (but then I guess the contents of that view shouldn't treat that frame as even remotely final? because how could it be when it was derived on an assumption? ugh...). Then re-set their frames in layoutSubviews. And make sure to manually handle the differences between initial layout and layout as a result of a moved view there... Man I feel like I've GOT to be missing something... (lol denial...)
I guess that, OR submit and use IB.
update2- viewWillLayoutSubviews WILL get called when one of its subviews is resized. So it is still disqualified as it fails property 2 of the required characteristics that I'm looking for. :(
If you're doing layout with IB, it's fine to do additional view initialization in viewDidLoad (for example, if you need to do stuff that IB doesn't handle well, or if you have UIView subclasses with properties not supported by IB). Alternatively, if you're not using IB, the documentation says you should use loadView to manually initialize your view hierarchy.
You're right, though, that you can't rely on the frame being accurate at that point. So you can accomplish layout via each view's autoResizingMask property, layout constraints (if you're iOS 6 and later), and/or overriding layoutSubviews.
My usual approach is to do layout to some degree in IB, then do anything else I need to (nontrivial layout, custom classes, etc) in viewDidLoad. Then, if I have layout to figure out that autoResizingMask doesn't cover (I'm supporting down to iOS 5), I override viewWillAppear (or layoutSubviews if I'm subclassing UIView) and do some pixel math. I've got a category on UIView to help with this that has things like:
-(void)centerSubviewHorizontally:(UIView *)view pixelsFromTop:(float)pixels;
-(void)centerSubviewHorizontally:(UIView *)view pixelsBelow:(float)pixels siblingView:(UIView *)sibling;
View controllers should not have initWithFrame: methods. What I do in all of my code (I never use IB) is to let the default loadView do its own thing. I create and setup all subviews in viewDidLoad. At this point the view controller's frame has at least a sane value. All subviews can be created with their own sane frames based on the initial size of the view controller's view. With proper autoresizingMask values this may be all you need.
If you need more specific subview layout, put the appropriate layout code in the viewWillLayoutSubviews method. This will deal with any view controller view frame changes including rotation, in-call status bars, etc.
If you don't use interface builder you should override loadView and initialize the views there. If you use autolayout you can also add your constraints there. If you don't use autolayout you can override the layoutSubviews method of your views to adjust the frames.
Is there a problem with adding a UITableView as a subview of aUIView? I had a line of code that was working for months, and recently broke after I updated XCode to 4.6.3. Long story short, I was returning TableViewTwo as a subview of a UIView as a footer of TableViewOne. Please don't ask me why this is necessary, it is just convenient for what I am doing. Well, the app would always crash on the simulator, but not on the device. It would give me a bizarre error and an opaque call stack. The error said unrecognized selector(numberOfSections)sent to instance. Yes, the selector was numberOfSections and not the numberOfSectionsInTableView in the UITableViewDataSource. Well, when I returned the UITableViewTwo as UITableViewOne's footer..everything started working. Does anyone know if there is a problem with adding a UITableView as a subview of a UIView? For more information - see this SO Post(link)
What are your best practising when you subclass UIView?
I'm trying to do a view for a viewcontroller entirely in code, using the new ios 6 autolayout.
So I've subclassed UIView and setup the basic constraints in #initWithFrame (should I do this in #updateConstraints?). The problem is that I have an UILabel with dynamic text and variable width so I have to setup #preferredMaxLayoutWidth after the constraints have determined a frame, so I think #layoutSubviews is the best place to do that, but doing that results in a NSInternalInconsistencyException: Auto Layout still required after executing -layoutSubviews. MyView's implementation of -layoutSubviews needs to call super. (RuntimeError)
Before you asks I'm sure I have called super in every method I overridden.
Any idea?
Perhaps what you need here is to not have your view call [super layoutSubviews], but UIView, the ancestor, call its [super layoutSubviews].
This can be achieved not by subclassing, but implementing a category of UIView, which would have layoutSubViews function implemented, calling super. If I could have your code I could have tried. Someone has tried it successfully here.
Again, this does not guarantee that this will be fixed. There seems to be many bug reports concerning this in Apple forums. Unfortunately, we can't browse others' bugs in Apple portal, we can just file our own.
As a workaround, I would also suggest if any of your constraints conflict with your
preferredMaxLayoutWidth argument: try increasing or decreasing it. As per the overriding order of its own, it can change things, and who (other than Apple) knows - it maybe fixed. Fingers crossed!
This seemed to work for me, I can't explain why, but it did.
-(void)layoutSubviews
{
[super layoutSubviews];
layout code here, including updated constraints
[super layoutSubviews];
}
I'm sorry if I missed something here, but I thought UIView objects that were created in IB should have their frames created in viewDidLoad so you can do initial setup based off of this view in viewDidLoad or awakeFromNib or viewWillAppear. I logged the output in each method:
NSLog(#"%# %s", NSStringFromCGRect(self.zoomView.frame), __FUNCTION__);
And in all I get {0, 0, 0, 0}.
This is the first nib in my UIStoryboard, and I'm using Autolayout and iOS 6. I could have sworn on previous apps I have used the frame of other UIView objects created in IB to set things up. Is there something that has changed? Or do I just remember it incorrectly? Thanks!
I had the exact same issue. Yes, you remember correctly - it used to be different in iOS 5. I always set up my views in viewDidLoad: and the frame was already the way it was going to be when the view was actually on screen.
Now in iOS 6, you'll need to put your code into viewDidAppear: to have a valid frame to work with, if auto layout is enabled. Apparently the laying-out is done in between those two calls.
I think it has to do with the device you are using and possibly other things but the frames and bounds "could" sometimes be set by the time viewDidLoad is invoked and sometimes not (depends on ....) On the other hand, frames and bounds are guaranteed to have been set by the time "ViewDidAppear" has been called. And you want to put your code related to frames and bounds where it is guaranteed!