161

I'm using Xcode 6 Beta 3, iOS 8 SDK. Build Target iOS 7.0 using Swift. Please refer to my problem step by step with screenshots below.

I have a UICollectionView in Storyboard. 1 Prototype UICollectionViewCell which contains 1 label in the centre (no autoresizing rule). Purple background was to mark a contentView that is generated in runtime by the Cell I guess. That view will be resized properly base on my UICollectionViewLayoutDelegate eventually, but not on iOS 7. Notice that I'm using Xcode 6 and the problem only happens on iOS 7.

When I build the app on iOS 8. Everything is okay.

Note: Purple is the contentView, Blue is my UIButton with rounded corner.

https://i.stack.imgur.com/uDNDY.png

However, on iOS 7, all the subViews inside the Cell suddenly shrink to the frame of (0,0,50,50) and never conforms to my Autoresizing rule anymore.

https://i.stack.imgur.com/lOZH9.png

I assume this is a bug in iOS 8 SDK or Swift or maybe Xcode?


Update 1: This problem still exists in the official Xcode 6.0.1 ! The best work around is like what KoCMoHaBTa suggested below by setting the frame in cellForItem of the cell (You have to subclass your cell though). It turned out that this is a incompatibility between iOS 8 SDK and iOS 7 (check ecotax's answer below quoted from Apple).

Update 2: Paste this code at the beginning of your cellForItem and things should be okay:

/** Xcode 6 on iOS 7 hot fix **/
cell.contentView.frame = cell.bounds;
cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
/** End of Xcode 6 on iOS 7 hot fix **/
halfer
  • 19,824
  • 17
  • 99
  • 186
thkeen
  • 1,857
  • 2
  • 13
  • 16
  • 1
    I found out that this problem still exists in Xcode 6 Beta 5. Anyone experienced this too? – thkeen Aug 13 '14 at 18:53
  • 2
    I am struggling with this right now in my project. iOS 7 and iOS 8 built using Xcode 5 look fine. iOS 8 built using Xcode 6 beta 6 looks fine. iOS 7 built using Xcode 6 beta 6 is having the problem you are describing. Using Reveal I can see that my UICollectionViewCell has been properly sized. But the cell's contentView was not resized, even though it's parent, the UICollectionViewCell has Autoresize Subviews turned on. The size of the contentView is set to what the storyboard has. I am not using autolayout in this project. My project is completely objective-c. – Del Brown Aug 28 '14 at 16:06
  • 2
    I just wanted to add that this issue still exists as of Xcode 6 / iOS 8 GM seed. @DanielPlamann's answer to force the `contentView` to resize with the cell works fine to workaround the problem. I guess that in iOS 8 Apple changed something about the way cell content views are handled when created in Interface Builder (which is still a bit of a black box anyway). But the fact that it changes behaviour when targeting iOS 7 is surely a bug. – Stuart Sep 10 '14 at 09:24
  • Same here with Xcode 6 GM, auto layout and a nib-based cell. I fix it by pinning the `contentView` edges to the cell edges. – sergiou87 Sep 10 '14 at 13:39
  • Yeah, this is not good. I don't want to put this workaround into a production app at all. Have anyone of you filed this as bug report yet? I'm afraid they don't take this seriously enough. – thkeen Sep 10 '14 at 18:04
  • @sergiou87 how did you manage to pin the `contentView` to the edges via the nib when `UICollectionViewCell`s don't show the `contentView` in the nib? – Acey Sep 10 '14 at 18:34
  • @Acey unfortunately that has to be done in code :-\ We're using a custom subclass of `UICollectionViewCell` as base class for our custom cells. – sergiou87 Sep 11 '14 at 20:02
  • Well, in fact it's not unfortunate, because doing it this way we avoid having to do that for every cell :D – sergiou87 Sep 11 '14 at 20:03
  • @Acey I just updated the best solution, either you're using nib or not. Putting the block of code in cellForItem is the safest way. – thkeen Sep 23 '14 at 06:29
  • Happened both on Xcode 6.0 (from AppStore) AND Xcode 6.1 Beta for Yosemite. 'Update 2' works like charm. I just spent half a day on that... – Aviel Gross Sep 23 '14 at 10:26
  • Apple appears to have fixed this problem in version 8.1 of the iOS SDK in Xcode 6.1 GM (Build 6A1042b). I removed the hack and my collection view cells were sized correctly. Can anyone else confirm this? – phatmann Oct 07 '14 at 16:21
  • Did you test on iOS 7? – thkeen Oct 08 '14 at 14:19
  • 3
    I downloaded xcode 6.1 but still see the same problem in the simulator. – Haitao Li Oct 23 '14 at 18:46
  • I never had this issue before iOS 8.1 update, then it started happening (in code compiled with 6.0.1 and also when I recompiled in 6.1). I had to add the workaround to get rid of it. – meelawsh Oct 24 '14 at 19:24
  • None of the solutions still work for me!! Any help? – thatzprem Dec 03 '14 at 06:05
  • http://stackoverflow.com/questions/27265576/static-uitableviewcell-subviews-are-resized-incorrectly-was-off-the-screen-ove I got another problem with static cell – thkeen Dec 03 '14 at 07:10
  • Guys, the sad news is that, Apple still leaves this bug until the latest iOS SDK. – thkeen Jan 15 '15 at 19:56

12 Answers12

170

contentView is broken. It can be also fixed in awakeFromNib

ObjC:

- (void)awakeFromNib {

    [super awakeFromNib];

    self.contentView.frame = self.bounds;
    self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}

Swift3:

override func awakeFromNib() {
    super.awakeFromNib()

    self.contentView.frame = self.bounds
    self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Igor Palaguta
  • 3,579
  • 2
  • 20
  • 32
  • 3
    Did the trick without self.contentView.frame = self.bounds; Think I need it? Thanks anyway! – Michal Shatz Sep 21 '14 at 09:10
  • Hello Michal, agree with you. But for 100% to be sure, how will it work in next iOSes better to add it I think. – Igor Palaguta Sep 21 '14 at 09:31
  • 5
    I like this answer but you need to call [super awakeFromNib]; Also, I'v thinking about adding a check for iOS 7.1 and less, because I'm not sure how adding these resizing masks affects default behavior on iOS8. – GingerBreadMane Sep 23 '14 at 14:56
  • If you're not using nibs or storyboards, this also works if you put it in applyLayoutAttributes: – cetcet Sep 25 '14 at 23:01
  • This doesn't work for me. I am not using Autolayout!! Any assistance? – thatzprem Dec 03 '14 at 06:22
  • How do you create cell? From nib? – Igor Palaguta Dec 03 '14 at 07:25
  • We were seeing an issue with ios 8.x that the collection view cell was not displaying all of it's subviews sometimes. This fixed our issues, but it is slightly different then the main question. – ort11 Dec 08 '14 at 19:05
  • I added same code in swift. But still app crashing and showing bad execute error at `layout2.estimatedItemSize = CGSize(width: 300, height: 50)` . I added a code in Cell subclass `self.contentView.frame = self.bounds; self.contentView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight`. amy one know how to rectify in swift – sudheer Dec 30 '14 at 11:08
  • Adding auto resize subviews in the Nib is the real trick. You don't anything else. – Deepak G M Jan 20 '15 at 13:40
  • What if storyboard table has static cells? – Mohammad Sadiq Shaikh Mar 19 '15 at 10:38
  • How can `contentView` be broken, still? – Alper Sep 20 '18 at 10:41
62

I encountered the same problem and asked Apple DTS for help. Their reply was:

In iOS 7, cells’ content views sized themselves via autoresizing masks. In iOS 8, this was changed, cells stopped using the autoresizing masks and started sizing the content view in layoutSubviews. If a nib is encoded in iOS 8 and then decode it on iOS 7, you’ll have a content view without an autoresizing mask and no other means by which to size itself. So if you ever change the frame of the cell, the content view won’t follow.

Apps being deploying back to iOS 7 will have to work around this by sizing the content view itself, adding autoresizing masks, or adding constraints.

I guess this means that it's not a bug in XCode 6, but an incompatibility between the iOS 8 SDK and iOS 7 SDK, which will hit you if you upgrade to Xcode 6, because it will automatically start using the iOS 8 SDK.

As I commented before, the workaround Daniel Plamann described works for me. The ones described by Igor Palaguta and KoCMoHaBTa look simpler though, and appear to make sense giving Apple DTS' answer, so I'll try those later.

ecotax
  • 1,933
  • 17
  • 22
  • This is interesting, but I still hope they fix it. This behavior was only introduced in Xcode 6 GM which added iPhone 6 support. It was working fine in the earlier betas. I even brought a project back to an earlier beta after I noticed it and it worked as expected. I hope everyone files bugs with Apple on this issue. – arton Sep 30 '14 at 03:36
  • @arton I filed a bug report the same day I asked DTS for help. It was closed as a Duplicate of 18312246. Not sure how much this helps. – ecotax Oct 01 '14 at 10:35
  • I've used the work around by sizing the ContentView and works for me. - (CGSize)collectionViewContentSize { return CGSizeMake(self.collectionView.bounds.size.width, self.collectionView.bounds.size.height); } – Jesús Hurtado Apr 30 '15 at 15:18
60

I encountered the same issue and hope that Apple will fix this with the next Xcode version. Meanwhile I use a workaround. In my UICollectionViewCell subclass I've just overridden layoutSubviews and resize the contentView manually in case the size differs from collectionViewCell size.

- (void)layoutSubviews
{
  [super layoutSubviews];

  BOOL contentViewIsAutoresized = CGSizeEqualToSize(self.frame.size, self.contentView.frame.size);

  if( !contentViewIsAutoresized) {
    CGRect contentViewFrame = self.contentView.frame;
    contentViewFrame.size = self.frame.size;
    self.contentView.frame = contentViewFrame;
  }
}
Bhumit Mehta
  • 16,278
  • 11
  • 50
  • 64
Daniel Plamann
  • 601
  • 4
  • 4
  • Yeah I used a similar work around but I did it in cellForItem / cellForRow which also works. – thkeen Sep 04 '14 at 04:20
  • 1
    This is the best solution. Adding this in cellForItem/cellForRow will cause weird artifacts if you're rotating the device and the cell size changes. – spybart Sep 12 '14 at 18:54
  • Hi! I got a problem with this code. For the first time, the boolean contentViewIsAutoresized will be true if it is loaded from storyboard or prototype cell. Only when you do a reloadData, the second it will be correct. So you don't really need to check for the size. Instead just do: self.contentView.frame = self.bounds; – thkeen Sep 18 '14 at 06:10
  • Confirmed: we should do it in cellForItem or cellForRow because layoutSubviews is only called after the cell is returned. Anything you do before that like drawing stuff will be wrongly calculated. – thkeen Sep 18 '14 at 06:18
  • 1
    Hm, maybe it's better to place `[cell layoutIfNeeded];` in cellForItem or cellForRow? – wtorsi Sep 22 '14 at 07:29
38

Another solution is to set the contentView's size and autoresizing masks in -collectionView:cellForItemAtIndexPath: like the following:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

     static NSString *cellID = @"CellID";

     UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];

     // Set contentView's frame and autoresizingMask
     cell.contentView.frame = cell.bounds;
     cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin |UIViewAutoresizingFlexibleTopMargin |UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;

     // Your custom code goes here

     return cell;
}

This works with Auto Layout too, since auto resizing masks are translated to constraints.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
KoCMoHaBTa
  • 1,519
  • 15
  • 16
  • 2
    This is a superior solution as it works with any cell type without subclassing and doesn't require changes in multiple places when you are using more than one type of cell in your collection view. – nacross Oct 14 '14 at 00:00
6

In Xcode 6.0.1 contentView for UICollectionViewCell is broken for iOS7 devices. It can be also fixed by adding proper constraints to UICollectionViewCell and its contentView in awakeFromNib or init methods.

        UIView *cellContentView = self.contentView;
        cellContentView.translatesAutoresizingMaskIntoConstraints = NO;

        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[cellContentView]|"
                                                                     options:0
                                                                     metrics:0
                                                                       views:NSDictionaryOfVariableBindings(cellContentView)]];
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[cellContentView]|"
                                                                     options:0
                                                                     metrics:0
                                                                       views:NSDictionaryOfVariableBindings(cellContentView)]];
SerJ_G
  • 184
  • 1
  • 4
  • Only this works now, you have to do it just for IOS 8, and I have to call it after each dequeue ! But this also is not ideal solution because multiple selection do not work as it should visually... – Renetik Jan 02 '15 at 14:25
  • Best is to use autolayout really, with this bug you save ironically time by use autolayout event in simple case.. – Renetik Jan 02 '15 at 14:50
  • The mask does not work for me; however setting the constraints works! – entropid Mar 18 '15 at 01:50
4

This will not work correctly without any of the other mentioned workarounds because of a bug in Xcode 6 GM with how Xcode compiles xib files into the nib format. While I cannot say for 100% certainty it is Xcode related and not having to do with runtime, I'm very confident - here's how I can show it:

  1. Build+Run the application in Xcode 5.1.
  2. Go to the simulator application's directory and copy the compiled .nib file for the xib you are having issues with.
  3. Build+Run the application in Xcode 6 GM.
  4. Stop the application.
  5. Replace the .nib file in the newly built application's simulator folder with the .nib file created using Xcode 5.1
  6. Relaunch the app from the simulator, NOT from Xcode.
  7. Your cell loaded from that .nib should work as expected.

I hope everyone who reads this question will file a Radar with Apple. This is a HUGE issue and needs addressing before the final Xcode release.

Edit: In light of ecotax's post, I just wanted to update this to say it is now confirmed behavior differences between building in iOS 8 vs iOS 7, but not a bug. My hack fixed the issue because building on iOS 7 added the autoresizing mask to the content view needed to make this work, which Apple no longer adds.

Community
  • 1
  • 1
Acey
  • 8,048
  • 4
  • 30
  • 46
  • I'm not sure if they legally can release a different xCode than the GM version – Mabedan Sep 16 '14 at 10:45
  • Haha will they go to jail? =P But in all seriousness, sure they can. They had multiple GM versions for Mavericks if you don't remember. It's not too common but it can happen. – Acey Sep 16 '14 at 15:41
4

The answers in this post work, what I never understood is why it works.

First, there are two "rules":

  1. For views created programmatically (Ex. [UIView new]), the property translatesAutoresizingMaskIntoConstraints is set to YES
  2. Views created in interface builder, with AutoLayout enabled, will have the property translatesAutoresizingMaskIntoConstraints set to NO

The second rule does not seem to apply to top-level views for which you do not define constraints for. (Ex. the content view)

When looking at a Storyboard cell, notice that the cell does not have its contentView exposed. We are not "controlling" the contentView, Apple is.

Deep dive into storyboard source code and see how contentView cell is defined:

<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">

Now the cell's subviews (notice the translatesAutoresizingMaskIntoConstraints="NO"):

<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NaT-qJ-npL" userLabel="myCustomLabel">

The contentView does not have it's translatesAutoresizingMaskIntoConstraints set to NO. Plus it lacks layout definition, maybe because of what @ecotax said.

If we look into the contentView, it does have an autoresizing mask, but no definition for it: <autoresizingMask key="autoresizingMask"/>

So there are two conclusions:

  1. contentView translatesAutoresizingMaskIntoConstraints is set to YES.
  2. contentView lacks definition of a layout.

This leads us to two solutions which have been talked about.

You can set the autoresizing masks manually in awakeFromNib:

self.contentView.frame = cell.bounds;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

Or you can set the contentView translatesAutoresizingMaskIntoConstraints to NO in awakeFromNib and define constraints in - (void)updateConstraints.

Community
  • 1
  • 1
kgaidis
  • 14,259
  • 4
  • 79
  • 93
4

This is the Swift version of @Igor's answer which is accepted and thanks for your nice answer mate.

First Goto your UICollectionViewCell Subclass and paste the following code as it is inside the class.

override func awakeFromNib() {
    super.awakeFromNib()
    self.contentView.frame = self.bounds
    self.contentView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
}

By the way I am using Xcode 7.3.1 and Swift 2.3. Solution is tested on iOS 9.3 which is working flawlessly.

Thanks, Hope this helped.

onCompletion
  • 6,500
  • 4
  • 28
  • 37
2

In swift, place the following code in the collection view cell subclass:

override var bounds: CGRect {
  didSet {
    // Fix autolayout constraints broken in Xcode 6 GM + iOS 7.1
    self.contentView.frame = bounds
  }
}
Forge
  • 6,538
  • 6
  • 44
  • 64
Ian
  • 12,538
  • 5
  • 43
  • 62
  • This was the answer I was looking for. I used to override setBounds in Objective-C to fix this problem (wasn't sure how to write it in Swift). Thank you :) – Matthew Cawley Oct 19 '16 at 01:34
1

I have found that there are also issues with contentView sizing in iOS 8. It tends to get laid out very late in the cycle, which can cause temporary constraint conflicts. To solve this, I added the following method in a category of UICollectionViewCell:

- (void)fixupContentView
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 80100
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
    if (NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) {
        self.contentView.frame = self.bounds;
        self.contentView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin |UIViewAutoresizingFlexibleTopMargin |UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
    } else {
        [self layoutIfNeeded];
    }
#endif
#endif
}

This method should be called after dequeuing the cell.

phatmann
  • 18,161
  • 7
  • 61
  • 51
  • It would be ideal if this method were called automatically. I don't love it, but this could be done by swizzling `dequeueReusableCellWithReuseIdentifier:forIndexPath:`. – phatmann Oct 03 '14 at 21:50
  • Apple appears to have fixed this problem in version 8.1 of the iOS SDK in Xcode 6.1 GM (Build 6A1042b). Therefore I have updated the code above to not run when using the 8.1 SDK. Once your team has all moved to Xcode 6.1 you can remove this hack entirely. – phatmann Oct 07 '14 at 16:15
1

I fixed doing this:

override func layoutSubviews() {
   contentView.superview?.frame = bounds
   super.layoutSubviews()
}

see: here

Community
  • 1
  • 1
Serluca
  • 2,102
  • 2
  • 19
  • 31
-1

Just make sure you check the check box "Autoresize subviews" in the nib for that collection view cell. It will work fine on both iOS 8 and iOS 7.

Deepak G M
  • 1,650
  • 17
  • 12