This is the view of the app when it is in portrait mode.
When it is rotated to landscape mode it looks like this
The view debugger shows that the UIWindow is not rotating as shown here
The UICollectionViewController is created via StoryBoard. I've tried subclassing UICollectionViewFlowLayout that implements shouldInvalidateLayoutForBoundsChange, but it does not fix my issue.
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
CGRect oldBounds = self.collectionView.bounds;
if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) {
return YES;
}
return NO;
}
Please provide ideas of what to check next or requests for additional code to debug.
Edit - As suggested by MirekE, I attempted to add constraints to the CollectionView but was unable. All of the options for Editor->Pin are unavailable for the CollectionView.
Edit, response to Andrea -
I'm targeting iOS8.3. My understanding is that the main method called at rotation is viewWillTransitionToSize:withTransitionCoordinator:, which is from the UIContentContainer protocol. I've added the following to my CollectionViewController, but same problem persists
-(void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator {
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView reloadData];
}
I don't know which iOS version you ara targeting, but let me suppose that you know the rotation process and methods called in the view controller while it's happening.
In one of those methods you just need to call:
[self.collectionView.collectionViewLayout invalidateLayout];
and maybe depending on your layout -reloadData
No need to subclass.
EDIT
I use this method, I guess that is not working because you should relayout the collection after it has resized:
- (void) willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self.collectionView.collectionViewLayout invalidateLayout];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self.collectionView reloadData];
}];
}
What I'm doing attach the layout invalidation process to the animation process.
If you have custom implementation of UICollectionViewLayout, try to override methods:
override public func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
let bounds = self.collectionView!.bounds;
return ((CGRectGetWidth(newBounds) != CGRectGetWidth(bounds) ||
(CGRectGetHeight(newBounds) != CGRectGetHeight(bounds))));
}
override public func invalidateLayout() {
cache.removeAll() // remove layout attributes ans settings if they exist
super.invalidateLayout()
}
Cheers !
It looks like you just drag the collection view on the canvas, but did not add any constraints. So when you rotate the device, the size does not change.
In the storyboard select the collection view, then click on the Pin icon at the bottom of Xcode and add constraints for the top, left, bottom and right margins. After you do that, the collection view should resize on rotation.
The problem was that the collectionView outlet was not properly set. After setting the collectionView, all works properly.
collectionView outlet not set:
collectionView outlet set:
Related
It seems that with Xcode 8, on viewDidLoad, all viewcontroller subviews have the same size of 1000x1000. Strange thing, but okay, viewDidLoad has never been the better place to correctly size the views.
But viewDidLayoutSubviews is!
And on my current project, I try to print the size of a button:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(#"%#", self.myButton);
}
The log shows a size of (1000x1000) for myButton! Then if I log on a button click, for example, the log shows a normal size.
I'm using autolayout.
Is it a bug?
Now, Interface Builder lets the user change dynamically the size of every view controllers in storyboard, to simulate the size of a certain device.
Before this functionality, the user should set manually each view controller size. So the view controller was saved with a certain size, which was used in initWithCoder to set the initial frame.
Now, it seems that initWithCoder do not use the size defined in storyboard, and define a 1000x1000 px size for the viewcontroller view & all its subviews.
This is not a problem, because views should always use either of these layout solutions:
autolayout, and all the constraints will layout correctly your views
autoresizingMask, which will layout each view which doesn't have any constraint attached to (note autolayout and margin constraints are now compatible in the same view \o/ !)
But this is a problem for all layout stuff related to the view layer, like cornerRadius, since neither autolayout nor autoresizing mask applies to layer properties.
To answer this problem, the common way is to use viewDidLayoutSubviews if you are in the controller, or layoutSubview if you are in a view. At this point (don't forget to call their super relative methods), you are pretty sure that all layout stuff has been done!
Pretty sure? Hum... not totally, I've remarked, and that's why I asked this question, in some cases the view still has its 1000x1000 size on this method. I think there is no answer to my own question. To give the maximum information about it:
1- it happends only when laying out cells! In UITableViewCell & UICollectionViewCell subclasses, layoutSubview won't be called after subviews would be correctly layed out.
2- As #EugenDimboiu remarked (please upvote his answer if useful for you), calling [myView layoutIfNeeded] on the not-layed out subview will layout it correctly just in time.
- (void)layoutSubviews {
[super layoutSubviews];
NSLog (self.myLabel); // 1000x1000 size
[self.myLabel layoutIfNeeded];
NSLog (self.myLabel); // normal size
}
3- To my opinion, this is definitely a bug. I've submitted it to radar (id 28562874).
PS: I'm not english native, so feel free to edit my post if my grammar should be corrected ;)
PS2: If you have any better solution, feel free not write another answer. I'll move the accepted answer.
Are you using rounded corners for your button ?
Try calling layoutIfNeeded() before.
Solution: Wrap everything inside viewDidLayoutSubviews in DispatchQueue.main.async.
// swift 3
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
// do stuff here
}
}
I know this wasn't your exact question, but I ran into a similar problem where as on the update some of my views were messed up despite having the correct frame size in viewDidLayoutSubviews. According to iOS 10 Release notes:
"Sending layoutIfNeeded to a view is not expected to move the view,
but in earlier releases, if the view had
translatesAutoresizingMaskIntoConstraints set to NO, and if it was
being positioned by constraints, layoutIfNeeded would move the view to
match the layout engine before sending layout to the subtree. These
changes correct this behavior, and the receiver’s position and usually
its size won’t be affected by layoutIfNeeded.
Some existing code may be relying on this incorrect behavior that is
now corrected. There is no behavior change for binaries linked before
iOS 10, but when building on iOS 10 you may need to correct some
situations by sending -layoutIfNeeded to a superview of the
translatesAutoresizingMaskIntoConstraints view that was the previous
receiver, or else positioning and sizing it before (or after,
depending on your desired behavior) layoutIfNeeded.
Third party apps with custom UIView subclasses using Auto Layout that
override layoutSubviews and dirty layout on self before calling super
are at risk of triggering a layout feedback loop when they rebuild on
iOS 10. When they are correctly sent subsequent layoutSubviews calls
they must be sure to stop dirtying layout on self at some point (note
that this call was skipped in release prior to iOS 10)."
Essentially you cannot call layoutIfNeeded on a child object of the View if you are using translatesAutoresizingMaskIntoConstraints - now calling layoutIfNeeded has to be on the superView, and you can still call this in viewDidLayoutSubviews.
If the frames are not correct in layoutSubViews (which they are not) you can dispatch async a bit of code on the main thread. This gives the system some time to do the layout. When the block you dispatch is executed, the frames have their proper sizes.
This fixed the (ridiculously annoying) issue for me:
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height);
}
Edit/Note: This is for a full screen ViewController.
Actually viewDidLayoutSubviews also is not the best place to set frame of your view. As far as I understood, from now on the only place it should be done is layoutSubviews method in the actual view's code. I wish I wasn't right, someone correct me please if it is not true!
I already reported this issue to apple, this issue exist since a long time, when you are initializing UIViewController from Xib, but i found quite nice workaround. In addition to that i found that issue in some cases when layoutIfNeeded on UICollectionView and UITableView when datasource is not set in initial moment, and needed also swizzle it.
extension UIViewController {
open override class func initialize() {
if self !== UIViewController.self {
return
}
DispatchQueue.once(token: "io.inspace.uiviewcontroller.swizzle") {
ins_applyFixToViewFrameWhenLoadingFromNib()
}
}
#objc func ins_setView(view: UIView!) {
// View is loaded from xib file
if nibBundle != nil && storyboard == nil && !view.frame.equalTo(UIScreen.main.bounds) {
view.frame = UIScreen.main.bounds
view.layoutIfNeeded()
}
ins_setView(view: view)
}
private class func ins_applyFixToViewFrameWhenLoadingFromNib() {
UIViewController.swizzle(originalSelector: #selector(setter: UIViewController.view),
with: #selector(UIViewController.ins_setView(view:)))
UICollectionView.swizzle(originalSelector: #selector(UICollectionView.layoutSubviews),
with: #selector(UICollectionView.ins_layoutSubviews))
UITableView.swizzle(originalSelector: #selector(UITableView.layoutSubviews),
with: #selector(UITableView.ins_layoutSubviews))
}
}
extension UITableView {
#objc fileprivate func ins_layoutSubviews() {
if dataSource == nil {
super.layoutSubviews()
} else {
ins_layoutSubviews()
}
}
}
extension UICollectionView {
#objc fileprivate func ins_layoutSubviews() {
if dataSource == nil {
super.layoutSubviews()
} else {
ins_layoutSubviews()
}
}
}
Dispatch once extension:
extension DispatchQueue {
private static var _onceTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block: (Void) -> Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}
Swizzle extension:
extension NSObject {
#discardableResult
class func swizzle(originalSelector: Selector, with selector: Selector) -> Bool {
var originalMethod: Method?
var swizzledMethod: Method?
originalMethod = class_getInstanceMethod(self, originalSelector)
swizzledMethod = class_getInstanceMethod(self, selector)
if originalMethod != nil && swizzledMethod != nil {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
return true
}
return false
}
}
My issue was solved by changing the usage from
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
to
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
So, from Did to Will
Super weird
Better solution for me.
protocol LayoutComplementProtocol {
func didLayoutSubviews(with targetView_: UIView)
}
private class LayoutCaptureView: UIView {
var targetView: UIView!
var layoutComplements: [LayoutComplementProtocol] = []
override func layoutSubviews() {
super.layoutSubviews()
for layoutComplement in self.layoutComplements {
layoutComplement.didLayoutSubviews(with: self.targetView)
}
}
}
extension UIView {
func add(layoutComplement layoutComplement_: LayoutComplementProtocol) {
func findLayoutCapture() -> LayoutCaptureView {
for subView in self.subviews {
if subView is LayoutCaptureView {
return subView as? LayoutCaptureView
}
}
let layoutCapture = LayoutCaptureView(frame: CGRect(x: -100, y: -100, width: 10, height: 10)) // not want to show, want to have size
layoutCapture.targetView = self
self.addSubview(layoutCapture)
return layoutCapture
}
let layoutCapture = findLayoutCapture()
layoutCapture.layoutComplements.append(layoutComplement_)
}
}
Using
class CircleShapeComplement: LayoutComplementProtocol {
func didLayoutSubviews(with targetView_: UIView) {
targetView_.layer.cornerRadius = targetView_.frame.size.height / 2
}
}
myButton.add(layoutComplement: CircleShapeComplement())
Override layoutSublayers(of layer: CALayer) instead of layoutSubviews in cell subview to have correct frames
If you need to do something based on your view's frame - override layoutSubviews and call layoutIfNeeded
override func layoutSubviews() {
super.layoutSubviews()
yourView.layoutIfNeeded()
setGradientForYourView()
}
I had the issue with viewDidLayoutSubviews returning the wrong frame for my view, for which I needed to add a gradient. And only layoutIfNeeded did the right thing :)
As per new update in ios this is actually a bug but we can reduce this by using -
If you are using xib with autolayout in your project then you have to just update frame in autolayout setting please find image for this .
I have made my collection view cells to be calculated programmatically and I'm using sizeForItemAtIndexPath method to set the cell's sizes.
But When the screen rotates ,I need to recalculate the sizes and set them for cells. So,How can I call that function again after screen rotates?
By the way, I know didRotateFromInterfaceOrientation method and the only problem I got is that I don't know how should I call set the cell's sizes again.
Thanks
Another way using newer UIViewController methods
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
[self.collectionView.collectionViewLayout invalidateLayout];
}];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
You can reload your collection view in this method: This method is called when device changes its orientation. Reload collectionview will call sizeForItemAtIndexPath.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[collectionView reloadData]; // change collectionView with your collection view
}
Calculating the dynamics of cell can be replaced with auto calculation.
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(estimatedWidth, estimatedHeight)];
Try this
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[collectionView reloadInputViews];
}
When using auto layout, the view's size is unknown when it is initialised, this brings a problem to me.
When using UIImageView, I wrote a category that can load image from my own CDN by setting the image URL to UIImageView, my CDN stores one image with different sizes so that difference devices can load the size it really needs.
I want to make my UIImageView be able to load the URL for the resolution it needs, but when my UIImageView get the URL, the size of it is not yet determined by auto layout.
So is there a way for UIView to know that the layout process for it has finished for the first time?
there is a method for UIView.You could override it.
-(void)layoutSubviews {
CGRect bounds =self.bounds;
//build your imageView's frame here
self.imageView=imageViewFrame.
}
In Swift 5.X
override func layoutSubviews() {
super.layoutSubviews()
let myFrame = = self.bounds
}
If you have other complex items in the custom view, don't forget to call super if you override layoutSubviews()...
Sadly, there is no straightforward way that a UIView can be notified that its constraints have been set. You can try a bunch of different things though,
Implement layoutSubviews function of a UIView, this is called whenever UIView's layout is changed.
Implement viewDidLayoutSubviews of the UIViewController that has it inside it. This function is called when all the layouts have been set. At this point you can your category function.
here's a test, I only use autolayout and I only use custom subclassed subviews. I do all auto layout in the initializer of the subclass:
This is for a "login button" that has no frame but is then set with autolayout:
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"viewDidLayoutSubviews");
}
}
-(void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"viewWillLayoutSubviews");
}
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"didAppear");
}
}
-(void)viewWillAppear:(BOOL)animated {
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"viewWillAppear");
}
}
-(void)viewDidLoad {
[super viewDidLoad];
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"viewDidLoad");
}
}
Here's the output. So, this means that my view finally has a frame when the
2015-08-25 01:28:27.789 [67502:1183631] viewDidLayoutSubviews
2015-08-25 01:28:27.790 [67502:1183631] viewWillLayoutSubviews
2015-08-25 01:28:27.790 [67502:1183631] viewDidLayoutSubviews
2015-08-25 01:28:28.007 [67502:1183631] didAppear
This means that the first time the login button has a frame is in the viewDidLayoutSubviews, this will look weird becuase this is the first pass of main view's subviews. There's no frame in ViewWillAppear although the view of the viewcontroller itself is already set before the login button. The entire UIView subclass's main view is also set when the viewcontroler's view is set, this happens before the login button as well. So, the subviews of the view are set after the parent view is set.
The point is this: if you plop the imageview information pull in the viewDidLayoutSubviews then you have a frame to work with, unless you set this UIImageView frame to the view of the ViewController by type casting then you will have the UIImageView's frame set in the viewDidLoad. Good luck!
I have an iOS app in which I need to know when a new view is completely visible on-screen; that is, when Autolayout has finished its calculations and the view has finished drawing.
ViewDidAppear seems to fire well before the view is completely visible. If I turn off Autolayout, the timing seems to line up as far as human perception goes, but I need to use Autolayout in this project (so this isn't a solution...just a test).
Is there any method that fires when Autolayout is done calculating? Or another method that fires when the view is ACTUALLY visible (since ViewDidAppear doesn't work for this)?
Thanks!
The following can be used to avoid multiple calls:
- (void) didFinishAutoLayout {
// Do some stuff here.
NSLog(#"didFinishAutoLayout");
}
and
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(didFinishAutoLayout)
object:nil];
[self performSelector:#selector(didFinishAutoLayout) withObject:nil
afterDelay:0];
}
I'm using viewDidLayoutSubviews for this. Apple's documentation says, "Called to notify the view controller that its view has just laid out its subviews."
If you watched 2018's WWDC about "High-Performance AutoLayout", you would know the answer to this question.
Technically, there is no such API method that will be called when autolayout has completed your view's layout. But when autolayout has completed the calculations, your view's setBounds and setCenter will be called so that your view gets its size and position.
After this, your view's layoutSubviews will be called. So, layoutSubviews can, to some degree, be thought of as the method that fires after autolayout has done calculations.
As to view controller's viewDidLayoutSubviews, this is a bit complicated. The documentation says:
When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout.
So when viewDidLayoutSubviews called on a view controller, only the view controller'view 's first-level subviews are guaranteed to be laid out correctly.
What it worked in my case was request layout after changed a constraint value:
self.cnsTableviewHeight.constant = 50;
[self layoutIfNeeded];
Later on override layoutSubviews method:
- (void) layoutSubviews { //This method when auto layout engine finishes
}
You can call setNeedsLayout also instead of layoutIfNeeded
I guess implementing viewDidLayoutSubviews is the correct way but I used an animation just to write the completion callback inside the same method.
someConstraint.constant = 100; // the change
// Animate just to make sure the constraint change is fully applied
[UIView animateWithDuration:0.1f animations:^{
[self.view setNeedsLayout];
} completion:^(BOOL finished) {
// Here do whatever you need to do after constraint change
}];
You might face this problem not just with UIViewControllers but also UIViews. If you have a subview and want to know if AutoLayout has updated it's bounds, here is the Swift 5 implementation,
var viewBounds: CGFloat = 0.0
var autoLayoutHasCompleted: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
// someSubView is the name of a view you want to check has changed
viewBounds = someSubView.bounds.width
}
override func layoutSubviews() {
if viewBounds != someSubView.bounds.width && !autoLayoutHasCompleted {
// Place your code here
autoLayoutHasCompleted = true
}
}
i have a UICollectionView with a UICollectionViewFlowLayout. I also implement the UICollectionViewDelegateFlowLayout protocol.
In my datasource i have a bunch of UIViewControllers which respond to a custom protocol so i can ask for their size and some other stuff.
In in the FlowLayout delegate when it asks for the sizeForItemAtIndexPath:, i return the item size which i get from my protocol. The ViewControllers which implement my protocol return a different item size depending on the orientation.
Now if i change the device orientation from portrait to landscape theres no problem (Items are larger in landscape) but if i change it back, i get this warning:
the item width must be less that the width of the UICollectionView minus the section insets left and right values.
Please check the values return by the delegate.
It still works but i don't like it to get warnings so maybe you can tell me what i am doing wrong. Also there is another problem. If i don't tell my collectionViews collectionViewLayout to invalidate in willAnimateRotationToInterfaceOrientation: the sizeForItemAtIndexPath: is never called.
Hope you understand what i mean. If you need additional information let me know :)
This answer is late, but the accepted answer didn't work for me. I sympathize with the OP in wanting a fire-and-forget UICollectionViewFlowLayout. I suggest that invalidating the layout in the view controller is in fact the best solution.
I wanted a single horizontal scrolling line of cells, centered in the view, in both portrait and landscape.
I subclassed UICollectionViewFlowLayout.
I overrode prepareLayout to recalculate the insets and then call [super prepareLayout].
I overrode the getter for collectionViewContentSize to make certain the content size was correct.
The layout didn't invalidate on its own even though the bounds were changing with the reorientation.
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// does the superclass do anything at this point?
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
// do whatever else you need before rotating toInterfaceOrientation
// tell the layout to recalculate
[self.collectionViewLayout invalidateLayout];
}
The UICollectionViewFlowLayout maintains the scroll position between orientations. The same cell will be centered only if it is square.
I used a combination of the two answers here to do what we are all trying to do really is make a collection view look like a table view but not use UITableView and get items in a single column (most likely)
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.collectionView.collectionViewLayout invalidateLayout];
}
I don't know if it can help to someone but none of the last answer help me because I'm using some UIViews into a UIScrollView.
I'm opening the UIViews programmatically depending on some events. So I can't use - (void) willRotateToInterfaceOrientation: and invalidateLayout or reloadData because:
1. invalidateLayout "occurs during the next view layout update cycle."
2. reloadData "For efficiency, the collection view only displays those cells and supplementary views that are visible."
What worked for me was use the width of the collectionView's parent (an UIView) in:
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
int currentWidth = self.frame.size.width;
UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)collectionView.collectionViewLayout sectionInset];
int fixedWidth = currentWidth - (sectionInset.left + sectionInset.right);
For some reason the collectionView frame had the width of portrait at the time to call this function... using int currentWidth = self.frame.width help me to fix it.
A variation on #meelawsh's answer, but in Swift, and without a self.sizeForCollectionView property:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
guard let collectionView = self.collectionView else {
return
}
if size.width > collectionView.frame.width {
// bigger
let animation: (UIViewControllerTransitionCoordinatorContext)->(Void) = {
context in
collectionView.collectionViewLayout.invalidateLayout()
}
coordinator.animateAlongsideTransition(animation, completion: nil)
} else {
// smaller
collectionView.collectionViewLayout.invalidateLayout()
}
}
iOS8: the only way I could silence the warning is to call invalidate during the transition when rotating to a larger size and before transition when rotating to a smaller size.
self.sizeForCollectionView = size; //used to determine collectionview size in sizeForItem
if (size.width <= size.height) {
[self.collectionView.collectionViewLayout invalidateLayout];
}
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
if (size.width > size.height) {
[self.collectionView.collectionViewLayout invalidateLayout];
}
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
}];
The #0mahc0's answer worked for me, but only partially.
I don't have the rotate problem, because I force my app be only in Portrait mode, but my solution can help others with the same problem.
The warning shows every time the view appears. The reason of the warning is very clear, we have defined section insets to left and right of UICollectionView. Check interface builder and you will see in metrics those values defined.
The #0mahc0's solution works, but I want to stretch the cell to max width, so I removed the insets of section, otherwise I will have a 'margin' left and right.
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsZero;
}
Instead of implementing the delegate method you can go to interface builder and change value to zero of right and left insets.
One solution is to use KVO and listen for a change to the frame of the superview for your collection view. Calling -reloadData will work and get rid of the warnings. There is an underlying timing issue here...
// -loadView
[self.view addObserver:self forKeyPath:#"frame" options:NSKeyValueObservingOptionNew context:nil];
// -dealloc
[self.view removeObserver:self forKeyPath:#"frame"];
// KVO Method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
[collectionView_ reloadData];
}