I'm trying to lay out six UIViews onto a UIViewController, but I can't make them appear correctly in the simulator. Could someone please explain what constraints to use here to make it appear the same on the simulator?
My desired result, as seen in interface builder:
My currently result:
I'm using storyboards, Xcode 6, iOS 8 and autolayout.
Thanks!
You don't need collectionViews, nor any trickery in code. This is easily achievable in AutoLayout in interface builder.
Btw, the number and type of constraints matters. You should keep it as common sense as possible so that you can maintain it, and keep their number to minimum for the compositing part of UI rendering to stay fast. My solution is thus less complex than #k6sandeep s.
1) Lay them all out evenly, select all 6 tiles by CMD+mouseclick and add these constraints.
2) select each tile individually and add a particular constraint as shown below.
They have the same size and "kiss" each other on all sides and main view.
Once done, interface builder will stop complaining about missing constraints and you will just recalculate frames to make it perfect.
If you are only ever going to display 6 views a CollectionView might would not be necessary. You could place a 6 views with a constraints to pin the corner views to the respective corners. Then add constraints for the horizontal spacing between each neighbouring row/column. And finally set equal width and height constraints from the first (or any view) to the others. This does mean there are more views and constraints with dragging and dropping but you wont need to worry about populating a collectionView datasource and (perhaps the best bit) you won't need to write any code.
Here is a simple gif image of what I tried. It is actually fun to do this. You have to make sure that all have equal width and equal height, the views in rows have aligning top and bottom edges, while the views in columns have aligning leading and trailing edges. Then, if you set the distance between adjacent views and the edges to zero, it will all be done.
The image below shows what I actually mean in a pictorial representation.
I had a similar problem and couldn't make it work with autolayout. Turns out a UICollectionView was perfect for this though.
For a 2 x 6 grid:
CGFloat screenWidth = [[UIScreen mainScreen]bounds].size.width;
CGFloat screenHeight = [[UIScreen mainScreen]bounds].size.height;
CGSize collectionSize = CGSizeMake(screenWidth, screenHeight);
CGFloat cellWidth = screenWidth / 2;
CGFloat cellHeight = (screenHeight - 44) / 3; // deduct title bar
CGSize cellSize = CGSizeMake(cellWidth, cellHeight);
CGPoint collectionPoint = CGPointMake(0, 0);
CGRect collectionFrame = {collectionPoint, collectionSize};
// set up collection view flow layout
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.minimumLineSpacing = 0;
flowLayout.minimumInteritemSpacing = 0;
flowLayout.itemSize = cellSize;
flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
// instantiate
self.categoryCollectionView = [[UICollectionView alloc] initWithFrame:collectionFrame collectionViewLayout:flowLayout];
Then you can return the cell contents in
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
Make sure you implement other necessary delegate methods of UICollectionView like numberOfItemsInSection.
Related
I'm trying to achieve horizontally scrolling collection view with only 1 line. As I found out the easiest way to achieve that is to prepare custom flow layout:
class CollectionViewLayoutHorizontal : UICollectionViewFlowLayout {
override init() {
super.init()
//that enables self-sizing cells
self.estimatedItemSize = CGSize(width: 1, height: 1)
self.scrollDirection = .horizontal
//that one should ensure that only one line fits
//using CGFloat.greatestFiniteMagnitude aka CGFLOATMAX breaks collection view completely
self.minimumInteritemSpacing = 1000
self.minimumLineSpacing = 10
}
}
All my cells are properly displayed - they are places next to each other at the middle of collection view even when they are much smaller.
But the problem is there is an extra empty space at the end (after last cell) that is the size of minimumInteritemSpacing which is something unexpected.
Does anyone solve that problem ?
I know this is an old question but it still has no clear answer.
I ran into this exact same problem just recently using Swift 4.2 on iOS 12. After some investigations I found out that if you set the minimumLineSpacing to anything less than its default value (i.e. 10) then flow layout will add this difference multiplied by (number of items - 1) as an extra space to the end of the collection view. (Looks like the total contentSize is calculated with the default value regardless?)
Anyway I was able to solve this by setting the minimumInteritemSpacing to be the same value as minimumLineSpacing, which was 2 for both in my case.
I know it doesn't make any sense, since my collection view is a one-line with horizontally scrolling collection view flow layout, so minimumInteritemSpacing should have no effect in laying out the items, but apparently it does and this does the trick of removing the extra space at the end.
Try to implement this by:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(70, 60);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsMake(0, 0, 0, 0);
}
And set the flow layout of the collection view:
-(void)setCollectionViewLayout {
UICollectionViewFlowLayout *flow = [[UICollectionViewFlowLayout alloc] init];
flow.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flow.minimumInteritemSpacing = 5;
flow.minimumLineSpacing = 5;
[yourCollectionview setCollectionViewLayout:flow];
}
Note : my collection view has constant height.
Set minimumInteritemSpacing = 0 (or some smaller value that fits to your collection design at end of scroll), if you do not need to set trailing space inside collection view.
For a vertically scrolling grid, minimumInteritemSpacing value represents the minimum spacing between items in the same row.
For a horizontally scrolling grid, minimumInteritemSpacing value represents the minimum spacing between items in the same column.
minimumInteritemSpacing spacing is used to compute how many items can fit in a single line, but after the number of items is determined, the actual spacing may possibly be adjusted upward.
For more, see Apple Developer Document: minimumInteritemSpacing
Solution for collectionViewLayout
It's caused by the width/height of the group being set as absolute / fractional. Make the scrolling side as estimated.
NSCollectionLayoutSize(widthDimension: .estimated(1.0),
heightDimension: .fractionalHeight(1))
In iOS 8 the UICollectionViewFlowLayout supports automatically resizing cells based on their own content size. This resizes the cells in both width and height according to their content.
Is it possible to specify a fixed value for the width (or height) of all the cells and allow the other dimensions to resize?
For a simple example consider a multi-line label in a cell with constraints positioning it to the sides of the cell. The multi-line label could be resized different ways to accommodate the text. The cell should fill the width of the collection view and adjust it's height accordingly. Instead, the cells are sized haphazardly and it even causes a crash when the cell size is larger than the non-scrollable dimension of the collection view.
iOS 8 introduces the method systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: For each cell in the collection view the layout calls this method on the cell, passing in the estimated size. What would make sense to me would be to override this method on the cell, pass in the size that is given and set the horizontal constraint to required and a low priority to the vertical constraint. This way the horizontal size is fixed to the value set in the layout and the vertical size can be flexible.
Something like this:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return attributes;
}
The sizes given back by this method, however, are completely strange. The documentation on this method is very unclear to me and mentions using the constants UILayoutFittingCompressedSize UILayoutFittingExpandedSize which just represent a zero size and a pretty large one.
Is the size parameter of this method really just a way to pass in two constants? Is there no way to achieve the behavior I expect of getting the appropriate height for a given size?
Alternate Solutions
1) Adding constraints that will be specify a specific width for the cell achieves the correct layout. This is a poor solution because that constraint should be set to the size of the cell's collection view which it has no safe reference to. The value for that constraint could be passed in when the cell is configured, but that also seems completely counterintuitive. This is also awkward because adding constraints directly to a cell or it's content view is causing many problems.
2) Use a table view. Table views work this way out of the box as cells have a fixed width, but this would not accommodate other situations like an iPad layout with fixed width cells in multiple columns.
It sounds like what you are asking for is a way to use UICollectionView to produce a layout like UITableView. If that's really what you want, the right way to do this is with a custom UICollectionViewLayout subclass (maybe something like SBTableLayout).
On the other hand, if you're really asking if there is a clean way to do this with the default UICollectionViewFlowLayout, then I believe there is no way. Even with iOS8's self-sizing cells, it is not straightforward. The fundamental problem, as you say, is that the flow layout's machinery provides no way to fix one dimension and let another respond. (In addition, even if you could, there would be additional complexity around needing two layout passes to size the multi-line labels. This might not fit with how self-sizing cells want to compute all sizing via one call to systemLayoutSizeFittingSize.)
However, if you still want to create a tableview-like layout with a flow layout, with cells that determine their own size, and respond naturally to the collection view's width, of course it is possible. There is still the messy way. I have done it with a "sizing cell", i.e., a non-displayed UICollectionViewCell that the controller keeps only for calculating cell sizes.
There are two parts to this approach. The first part is for the collection view delegate to calculate the correct cell size, by taking in the collection view's width and using the sizing cell to calculate the cell's height.
In your UICollectionViewDelegateFlowLayout, you implement a method like this:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
This will cause the collection view to set the width of the cell based on the enclosing collection view, but then ask the sizing cell to calculate the height.
The second part is to get the sizing cell to use its own AL constraints to calculate the height. This can be harder than it should be, because of the way multi-line UILabel's effectively require a two-stage layout process. The work is done in the method preferredLayoutSizeFittingSize, which is like so:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(This code is adapted from the Apple sample code from the sample code of the WWDC2014 session on "Advanced Collection View".)
A couple points to notice. It's using layoutIfNeeded() to force layout of the entire cell, in order to compute and set the width of the label. But that's not enough. I believe you also need to set preferredMaxLayoutWidth so that the label will use that width with Auto Layout. And only then can you use systemLayoutSizeFittingSize in order to get the cell to compute its height while taking the label into account.
Do I like this approach? No!! It feels way too complex, and it does layout twice. But as long as performance doesn't become an issue, I'd rather perform layout twice at runtime than have to define it twice in code, which seems to be the only other alternative.
My hope is that eventually self-sizing cells will work differently and this will all get a lot simpler.
Example project showing it at work.
But why not just use self-sizing cells?
In theory, iOS8's new facilities for "self-sizing cells" should make this unnecessary. If you've defined a cell with Auto Layout (AL), then the collection view should be smart enough to let it size itself and lay itself out correctly. In practice, I haven't seen any examples that have gotten this to work with multi-line labels. I think this is partly because the self-sizing cell mechanism is still buggy.
But I'd bet it's mostly because of the usual trickiness of Auto Layout and labels, which is that UILabels require a basically two-step layout process. It's not clear to me how you can perform both steps with self-sizing cells.
And like I said, this is really a job for a different layout. It is part of flow layout's essence that it positions things that have a size, rather than fixes a width and lets them choose their height.
And what about preferredLayoutAttributesFittingAttributes: ?
The preferredLayoutAttributesFittingAttributes: method is a red herring, I think. That is only there to be used with the new self-sizing cell mechanism. So this isn't the answer as long as that mechanism is unreliable.
And what's up with systemlayoutSizeFittingSize:?
You're right the docs are confusing.
The docs on systemLayoutSizeFittingSize: and systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: both suggest that you should only pass UILayoutFittingCompressedSize and UILayoutFittingExpandedSize as the targetSize. However, the method signature itself, the header comments, and the behavior of the functions indicate that they are responding to the exact value of the targetSize parameter.
In fact, if you set the UICollectionViewFlowLayoutDelegate.estimatedItemSize, in order to enable the new self-sizing cell mechanism, that value seems to get passed in as the targetSize. And UILabel.systemLayoutSizeFittingSize seems to return the exact same values as UILabel.sizeThatFits. This is suspicious, given that the argument to systemLayoutSizeFittingSize is supposed to be a rough target and the argument to sizeThatFits: is supposed to be a maximum circumscribing size.
More Resources
While it is sad to think that such a routine requirement should require "research resources", I think it does. Good examples and discussions are:
http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html
http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html
code for WWDC2014 session 232, "Advanced User Interfaces with Collection Views"
There's a cleaner way to do this than some of the other answers here, and it works well. It should be performant (collection views load fast, no unnecessary auto layout passes etc), and doesn't have any 'magic numbers' like a fixed collection view width. Changing the collection view size, e.g. on rotation, and then invalidating the layout should work great too.
1. Create the following flow layout subclass
class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {
// Don't forget to use this class in your storyboard (or code, .xib etc)
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
guard let collectionView = collectionView else { return attributes }
attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
return attributes
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let allAttributes = super.layoutAttributesForElementsInRect(rect)
return allAttributes?.flatMap { attributes in
switch attributes.representedElementCategory {
case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
default: return attributes
}
}
}
}
2. Register your collection view for automatic sizing
// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)
flowLayout.estimatedItemSize = flowLayout.itemSize
3. Use the predefined width + custom height in your cell subclass
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return layoutAttributes
}
A simple way to do it in iOS 9 in a few lines of codes - the horizontal way exemple (fixing its height to its Collection View height) :
Init your Collection View Flow Layout with an estimatedItemSize to enable self-sizing cell :
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);
Implement the Collection View Layout Delegate (in your View Controller most of the time), collectionView:layout:sizeForItemAtIndexPath: . The goal here is to set the fixed height (or width) to the Collection View dimension. The 10 value can be anything, but you should set it to a value that doesn't break constraints :
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}
Override your custom cell preferredLayoutAttributesFittingAttributes: method, this part actually calculate your dynamic cell width based on your Auto Layout constraints and the height you have just set :
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
CGRect frame = attributes.frame;
frame.size.width = desiredWidth;
attributes.frame = frame;
return attributes;
}
Try fixing your width in the preferred layout attributes:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [[super preferredLayoutAttributesFittingAttributes:layoutAttributes] copy];
CGSize newSize = [self systemLayoutSizeFittingSize:CGSizeMake(FIXED_WIDTH,layoutAttributes.size) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
CGRect newFrame = attr.frame;
newFrame.size.height = size.height;
attr.frame = newFrame;
return attr;
}
Naturally you also want to ensure that you setup your layout correctly to:
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(FIXED_WIDTH, estimatedHeight)];
Heres something I put on Github that uses constant width cells and supports dynamic type so the height of the cells updates as the system font size changes.
YES it can be done using auto layout programmatically and by setting constraints in storyboard or xib. You need to add constraint for width size to remain constant and set height greater than or equal to.
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
Hope this will be helpful and solve your issue.
I've got two problems with my UICollectionView:
minimumInteritemSpacing doesn't work
it overflows horizontally on iOS 6
I set up the layout like this:
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = CGSizeMake(70.0f, 70.0f);
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.minimumLineSpacing = 0.0f;
layout.minimumInteritemSpacing = 0.0f;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
// I set the size of _collectionView in layoutSubviews:
// _collectionView.frame = self.bounds;
_collectionView.contentInset = UIEdgeInsetsMake(8.0f, 8.0f, 8.0f, 8.0f);
The image shows the result on iOS 6 (on iOS 7 there is no overflow, but the spacing between columns is still not zero)
I tried this solution https://gist.github.com/OliverLetterer/5583087, but it doesn't fix anything in my case.
From the documentation for the minimumInterItemSpacing property:
For a horizontally scrolling grid, this value represents the minimum spacing between items in the same column. This spacing is used to compute how many items can fit in a single line, but after the number of items is determined, the actual spacing may possibly be adjusted upward.
The flow layout will evenly space cells across its width, with a spacing of no smaller than the minimum you set. If you don't want the spacing, you'll need to implement your own layout.
The iOS 6 overflow issue I'm not sure about. Try dropping support for iOS 6 ;)
"line spacing" can mean the space between vertical lines.
Say you have a ordinary single line horizontal collection view.
(Eg, the whole view is simply 50 high, and the items are simply 50x50.)
As user #matt has explained
a horizontal collection view has columns of cells
the "lines" as Apple means it are the vertical "lines" (!) of cells
Thus:
in the case of a simple horizontal collection view with one row,
what Apple names the "line" spacing is - indeed - the spacing between items
Thus surprisingly in a simple horizontal collection view with one row, to set the gap between items, it's just:
l.minimumLineSpacing = 6 // Apple means "vertical scan lines" by "lines"
(minimumInteritemSpacing is completely meaningless in a normal simple horizontal collection view with one row.)
This finally explains why there are 100 pages on the internet asking why minimumInteritemSpacing just doesn't work. Fantastic tip by user #matt
Am fooling around with this question a couple of days now but no progress. What i want to do is quite simple i think:
I have an image of 320x60 which i use in the plain TableView which works oke as those cells take up the entire width (320) of the screen. The grouped cells in a TableView are 300 wide and have insets/margins left of 10 on the left and the right.
Can i somehow remove those insets/margins and let the grouped cell be 320 wide? I tried setting the content inset left to -10. That does "remove" the left margin but then it's still only 300 wide. Also tried editing the XML of the storyboard (I'm working with iOS 5 - Storyboards) but no joy.
This similar question here got answered as no it's not possible, hopfully something changed in 2+ years!:
Adjust cell width in grouped UITableView
PS i want to alter the width as the background images contain nice shadows, I've read that exesive use of shadows could mean performance issues. Also the shadow's are 5px extra around the border so that would mean -10px wide if I use the standard width.
Help much appreciated!
An untidy solution is to make the table view 340 pixels wide, and 10 pixels off the left edge of the screen.
A solution that involves changing properties of private classes is to make a UITableViewCell subclass, and override its layoutSubviews method. When I log the subviews, I find these:
"<UIGroupTableViewCellBackground: 0x95246b0; frame = (9 0; 302 45); autoresize = W; layer = <CALayer: 0x95226b0>>",
"<UITableViewCellContentView: 0x92332d0; frame = (10 0; 300 43); layer = <CALayer: 0x9233310>>",
"<UIView: 0x95248c0; frame = (10 0; 300 1); layer = <CALayer: 0x951f140>>"
What happens if we take those subviews and fill the entire bounds available?
- (void)layoutSubviews;
{
// By default the cell bounds fill the table width, but its subviews (containing the opaque background and cell borders) are drawn with padding.
CGRect bounds = [self bounds];
// Make sure any standard layout happens.
[super layoutSubviews];
// Debugging output.
NSLog(#"Subviews = %#", [self subviews]);
for (UIView *subview in [self subviews])
{
// Override the subview to make it fill the available width.
CGRect frame = [subview frame];
frame.origin.x = bounds.origin.x;
frame.size.width = bounds.size.width;
[subview setFrame:frame];
}
}
At this particular moment, on the iOS 5.1 simulator, this works. Now, some future version of iOS may restructure these classes, causing this method to catastrophically mangle the table layout. Your app could be rejected for changing the properties of UITableViewCellContentView... even though you're only modifying its frame. So, how much do you need to have your cells fill the table width?
You can the UITableView's Leading and Trailing Space constraints in the Size Inspector which is accessible via the Storyboard. I'm not sure when this was added, but setting the Leading Space Constraint to -10 and the Trailing Space Constraint to 10 will make the cells full width.
I'm trying to implement a grid (think .NET's DataGridView) for my iOS application. I'm using a UITableView with a custom UITableViewCell subclass in which I have several labels representing each column of data for that row. My question has to do with resizing the width of those labels after a device orientation change. I basically want to proportionally resize them so that, for example, a column which takes up 1/3 of the grid width in portrait orientation will still take up 1/3 of the width when switched to landscape.
Right now I'm achieving this by overriding the - (void)layoutSubviews method of my UITableViewCell subclass; I basically just hard code the iPad and iPhone landscape to portrait ratio. However, this only works if the grid itself is resized proportionally on an orientation change, which is certainly not always the case.
To achieve a solution that will work in any case, I thought about using self.bounds.size.width in the layoutSubviews method, but it seems as though autoresizing masks are the better solution. If I set each UILabel's mask to UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleLeftMargin | UIViewAutoResizingFlexibleRightMargin then I should achieve the type of scaling that I'm looking for. However, what I can't figure out is how to set the initial frames of each label. If I don't know the starting bounds of the cell until layoutSubviews is called, how can I arrange my subviews to begin with?
Try using self.contentView.bounds.size as the base for your calculations. For example:
int numberOfLabels = 3;
CGFloat marginHorizontal = 10;
CGFloat spacingHorizontal = 8;
CGFloat totalWidth = cell.contentView.bounds.size.width;
// That's how much is available to all labels
CGFloat availableWidth = (totalWidth
- 2 * marginHorizontal
- (numberOfLabels - 1) * spacingHorizontal);
// That's how much each label gets - initially!
CGFloat labelWidth = availableWidth / numberOfLabels;
Don't let it bother you that the content view initially has only a width of 320. It will be resized later, and your labels with it.
By the way: You should probably specify different masks for the labels at the left and the right edge:
Left edge: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin
Right edge: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin