I have collection view with custom flow layout and i wonder why i have extra space like that (right side):
It may hard to notice here, though, but you may see gray gap between right side and cell.
Here is how i override standard collection view flow layout to remove horizontals gaps:
#implementation CalendarFlowLayout
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *answer = [super layoutAttributesForElementsInRect:rect];
NSLog(#"layout blck passed");
for(int i = 1; i < [answer count]; ++i) {
UICollectionViewLayoutAttributes *currentLayoutAttributes = answer[i];
UICollectionViewLayoutAttributes *prevLayoutAttributes = answer[i - 1];
NSInteger maximumSpacing = 0;
NSInteger origin = CGRectGetMaxX(prevLayoutAttributes.frame);
if(origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize.width) {
CGRect frame = currentLayoutAttributes.frame;
frame.origin.x = origin + maximumSpacing;
currentLayoutAttributes.frame = frame;
}
}
return answer;
}
#end
Cell size calculated as follow:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(SCREEN_WIDTH/7, (SCREEN_HEIGHT-NAVBAR_HEIGHT-STATUS_BAR_HEIGHT)/6);
}
Those macros above look like that:
#define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width
It has been tested and it return correct output.
Also, if i set width to SCREEN_WIDTH/3, there is will be no gap.
How to remove it? Why is that happen?
375 / 3
= 125
375 / 7
= 53.57142857142857
You can't really light a half a pixel, so it is probably rounding down, leaving you with a gap. Make sure your size is a multiple of your subview size (or add a pixel to the rightmost object if it's not).
The problem is that the width of the cell (eg. SCREEN_WIDTH/3) is not a divisor of SCREEN_WIDTH. Correct divisors of SCREEN_WIDTH (375, as you said in comments) are 3, 5, 25 and 125 (125*3=375).
But As there are different screen size among all iOS devices, I think you should manage the problem differently. For example, you should choose a specific cell width and try to center the collection view in its container, so the extra-space will always be divided on both left and right side of the collection view.
Related
Updated:
Sample project is github link
USE iPHONE 6 simulator
2 possible ways to achieve this bug.
Just build and run and see.
Or uncomment 256 row
//return CGSizeMake(widthAndHeight * self.venueLayoutZoom, widthAndHeight * self.venueLayoutZoom);
and press "Remove column" button
I have UICollectionView with a lot of UICollectionViewCells. My UI makes me to use zero space between cells. I have a pinch gesture in my UICollectionView. So sometimes sizeForItem:atIndexPath: returns me a big float numbers (like 11.821123411231). The problem is:
If I don't round these floats I have a strange behaviour sometimes -
It should be
If I round up or down sizeForItem:atIndexPath: it looks great but there are spaces between cells
I don't know what to do. It is really necessary to do without these spaces or strange cells.
My flow layout is this
Controller Code:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
CGFloat widthAndHeight = [self widthAndHeightForCollectionView:collectionView];
CGFloat result = widthAndHeight * self.venueLayoutZoom;
return CGSizeMake(result, result);
}
- (void)didReceivePinchGesture:(UIPinchGestureRecognizer *)gesture
{
static CGFloat scaleStart;
if (gesture.state == UIGestureRecognizerStateBegan) {
scaleStart = self.venueLayoutZoom;
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
self.venueLayoutZoom = scaleStart * gesture.scale;
}
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
VenueLayoutCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kVenueLayoutCellReuseIdentifier forIndexPath:indexPath];
self.activeCollectionViewCellsDictionary[indexPath] = cell;
if (self.activeCollectionViewObjects.count > indexPath.section) {
NSArray *rows = self.activeCollectionViewObjects[indexPath.section];
if (rows.count > indexPath.row) {
if ([rows[indexPath.row] isKindOfClass:[VenueLayoutCellObject class]]) {
VenueLayoutCellObject *object = rows[indexPath.row];
cell.cellObject = object;
}
}
}
return cell;
}
Cell Code:
#property (nonatomic, strong) CALayer *circularLayer;
- (void)awakeFromNib
{
[super awakeFromNib];
[self allocAndAddLayers];
}
- (void)allocAndAddLayers
{
self.circularLayer = [CALayer layer];
[self.layer addSublayer:self.circularLayer];
}
#pragma mark - Property Set
- (void)setCellObject:(VenueLayoutCellObject *)cellObject
{
self->_cellObject = cellObject;
self.objectBackgroundColor = cellObject.objectColor;
self.type = cellObject.type;
}
- (void)prepareForReuse
{
[self.circularLayer removeFromSuperlayer];
[self allocAndAddLayers];
}
- (void)setType:(VenueLayoutObjectType)type
{
self->_type = type;
[self updateInderfaceDependOnType];
}
- (void)layoutSubviews
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
[self updateRoundedCorners];
[CATransaction commit];
[super layoutSubviews];
}
- (void)updateRoundedCorners
{
CGRect bounds = self.bounds;
CGRect frame = self.frame;
self.circularLayer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
}
- (void)setLayerBackgroundColor:(UIColor *)color
{
self.circularLayer.backgroundColor = color.CGColor;
self.objectMaskLayer.strokeColor = color.CGColor;
self.deselectedColor = color;
}
- (void)updateInderfaceDependOnType
{
UIColor *tempObjectColor = [UIColor grayColor];
UIColor *objectColor = self.objectBackgroundColor ? : tempObjectColor;
[self setLayerBackgroundColor:objectColor];
}
Updated Code:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
CGFloat widthAndHeight = self.widthAndHeightForActiveCollectionView;
CGFloat result = lroundf(widthAndHeight * self.venueLayoutZoom);
CGFloat width = result;
if (indexPath.row > self.increasedWidthInitialIndex) {
width++;
}
return CGSizeMake(width, result);
}
CGFloat widthAndHeight = CGRectGetWidth(self.activeCollectionView.bounds) / maxCount;
NSNumber *widthNumber = #(CGRectGetWidth(self.activeCollectionView.bounds));
NSInteger count = widthNumber.integerValue % maxCount;
maxCount--;
self.increasedWidthInitialIndex = maxCount - count;
Updated:
If I use low item size (e.g CGSizeMake(7.f, 7.f)) cells doesn't fit whole
collection view but space still exists
Eventually setting width / height to half pixels changes the scale according to the device pixel depth (retina vs non-retina) - more info here
When you try and divide your screen to non-whole numbers, i.e. for screen width of 320 create 6 cells of 53.3 with might cause the UI to break.
You should try and find the remainder of your devision, i.e:
320 % 6 -> 2
And then make 2 cells 1 pixel wider.
So instead of 6 cells consisting of 53.3 pixels wide, you will have 2 consisting of 54 and 4 more of 53, then everything will fit correctly.
EDIT:
After going over your project, I have added printouts of your collection see what issues could be caused by widths, Iv'e added this method in the ViewController.m:
- (void)printoutGrid
{
NSLog(#"collectionview bounds: %f, %#", self.activeCollectionView.bounds.size.width, NSStringFromCGSize(self.activeCollectionView.contentSize));
CGFloat calculatedWidth = [self widthAndHeightForActiveCollectionViewItem];
CGFloat result = floor(calculatedWidth * self.venueLayoutZoom);
for (int rowAt = 0; rowAt < self.activeCollectionViewObjects.count; rowAt++)
{
NSArray* arrayAt = self.activeCollectionViewObjects[rowAt];
NSString* rowString = #"|";
for (int colAt = 0; colAt < arrayAt.count; colAt++)
{
if (colAt > self.increasedWidthInitialIndex)
{
rowString = [rowString stringByAppendingString:[NSString stringWithFormat:#"%d|", (int)result + 1]];
}
else
{
rowString = [rowString stringByAppendingString:[NSString stringWithFormat:#"%d|", (int)result]];
}
}
NSLog(#"%#", rowString);
}
}
And called it after every time you reload the collection or invalidate the layout, Iv'e found only that you should use floor instead of lroundf.
This method will help you debug your widths for the future.
But, after going over your project settings, something seemed off in your simulator, I noticed it was too large / out of scale and not retina, I went to your project settings and saw that your project is not retina compatible - which means the UI will not use Auto-Scaling -> which means it will use the same resolution (320 x 518) for all devices and what it does, it stretches the UI - meaning in iPhone 6 it multiplies it, and the stretching is being anti-analysed, which causes these so-called empty spaces between cells even though there is not space there.
What you need to do, is go to your project settings -> and in general scroll down to "Launch Screen File" and select "LaunchScreen" from the list:
This seems to solve the issue for this project. Let me know if you have anymore question and if it works.
Please note - it will require you to change size calculation a-bit because the resolution changes again after "viewDidLayoutSubviews" and not only when the view loads.
Good luck!
I have five rows of cells each row containing four cells - 5x4. I am trying to accomplish almost an Apple watch like effect. The row in the center of the screen should have cell sizes of 100x100 and the rest return sizes of 80x80. When scrolled, the row moving away from the center should turn to 80x80 and the row moving into the center should turn 100x100.
I have implemented prepareLayout, layoutAttributesForElementsInRect, and layoutAttributesForItemAtIndexPath
So right now I have a center row of 100x100 cells and the rest 80x80.
To make it dynamic I implement shouldInvalidateLayoutForBoundsChange but nothing happens.
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count];
[self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier,
NSDictionary *elementsInfo,
BOOL *stop) {
[elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath,
UICollectionViewLayoutAttributes *attributes,
BOOL *innerStop) {
// NSLog(#"%f, %f", newBounds.origin.x, newBounds.origin.y);
if ((newBounds.origin.y < [[UIScreen mainScreen]bounds].size.height/2-50) && (newBounds.origin.y > [[UIScreen mainScreen]bounds].size.height/2+50)) {
CGRect frame = attributes.frame;
frame.size.width = 100.0f;
frame.size.height = 100.0f;
attributes.frame = frame;
}else{
CGRect frame = attributes.frame;
frame.size.width = 80.0f;
frame.size.height = 80.0f;
attributes.frame = frame;
}
[allAttributes addObject:attributes];
}];
}];
return YES;
}
shouldInvalidateLayoutForBoundsChange: is called pretty frequently (whenever the view is resized, or scrolled...), so you probably don't need to be doing all that work there. If you know your cells will always have a fixed size and position then you can just return NO here.
As long as you have implemented prepareLayout, layoutAttributesForElementsInRect, and layoutAttributesForItemAtIndexPath, then make sure you also implement collectionViewContentSize to return the total size of the content.
I am writing an app for the first time and I need it to pull images from a database and list them for the user to click on. My apologies ahead of time if this is hard to follow. The problem I am having is I need to put multiple images in one cell (Or as mentioned in the title simply have multiple cells per row), and I need each image to direct to its own unique detail page. So two questions: Is it possible to put multiple cells in one row (based off of the screen width)? and Is there an exception that I can write to stop looping when an Array goes out of bounds?
The images I am pulling all have the same dimensions (360x125) so I grab the width and height of the container based off of the orientation and adjust their size according to how many I can fit in a cell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Array the images are stored in.
Companies *item = _feedItems[indexPath.row];
// I grab the screen size and adjust if the screen is in landscape
CGRect result = [UIScreen mainScreen].bounds;
CGFloat width = CGRectGetWidth(result);
CGFloat height = CGRectGetHeight(result);
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
result.size = CGSizeMake(width, height);
} else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
result.size = CGSizeMake(height, width);
}
// if the width is less than 480 I fit one image and set the cells height,
// else I set the height to that if there were two images.
if(result.size.width <= 480) {
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds);
}
if(result.size.width > 480) {
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds) / 2;
}
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds);
}
If the width is greater than 480 then I put two images instead of one. This may increase to three images per line later, I have not got around to testing this on the iPad yet.
Then I draw the images into the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIndentifier = #"BasicCell";
UITableViewCell *myCell = [tableView dequeueReusableCellWithIdentifier:cellIndentifier];
myCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentifier];
CGRect result = [UIScreen mainScreen].bounds;
CGFloat width = CGRectGetWidth(result);
CGFloat height = CGRectGetHeight(result);
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
result.size = CGSizeMake(width, height);
} else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
result.size = CGSizeMake(height, width);
}
UIImageView *myImageView;
UIImageView *myImageView2;
if(result.size.width <= 480) {
Companies *item = _feedItems[indexPath.row];
myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,CGRectGetWidth(self.listTableView.bounds), (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds))];
myImageView.tag = 1;
myImageView.image = item.imageName;
[myCell addSubview:myImageView];
}
if(result.size.width > 480) {
Companies *item = _feedItems[indexPath.row * 2];
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,CGRectGetWidth(self.listTableView.bounds) / 2, (item.imageName.size.height / item.imageName.size.width) / 2 * CGRectGetWidth(self.listTableView.bounds))];
myImageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.listTableView.bounds) / 2,0,CGRectGetWidth(self.listTableView.bounds) / 2, (item.imageName.size.height / item.imageName.size.width) / 2 * CGRectGetWidth(self.listTableView.bounds))];
myImageView.tag = 1;
myImageView2.tag = 2;
myImageView.image = item.imageName;
myImageView2.image = item2.imageName;
[myCell addSubview:myImageView];
[myCell addSubview:myImageView2];
}
return myCell;
}
This is the code I have so far, and it will work normally when the orientation is portrait. However when the orientation is set to landscape I get an expected error of NSRangeException index beyond bounds. Because of this
Companies *item = _feedItems[indexPath.row * 2];
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
I want to pull both the image and the image that will come after it, to put in the same cell (Sorry if that is confusing). This is why I use y = 2x and y = 2x + 1. The problem is when x exceeds the highest value for y. So if the Array has 8 images [0 - 7], it will loop through the cell draw process 8 times, if I draw two at once it will draw the 8th image on the 3rd loop: 0[0,1] 1[2,3] 2[4,5] 3[6,7] The error will come up on the 4th loop: 4[8,9] index 8 beyond bounds 7. Is there an exception I can write to avoid this? Or am I using an incorrect thought process that should be handled differently?
How can I make it so that once I do put two images in the same row, each one will have their own on-click event handler and redirect to their own unique page (right now the call is on the table cell view, so that when I click either image in the same row, the page will redirect to the same one).
My restrictions are: I need to pull the data as I am from a mysql database through a php page. The number of images that will be pulled will be random. The images are 360 x 125 so I cannot stretch them too much, and thus need to have multiple ones on the same row should the user flip to landscape view or use a larger tablet or phone. Is there a better way to do this?
To prevent out of bounds error, just do a check on item count like this:
if (_feedItems.count < (indexPath.row*2+1)){
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
// rest of set up for second image
}
For detecting image press events, add a tap gesture recognizer to each image.
myImageView.userInteractionEnabled = YES; // This is important!
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onImageTap:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[tap setDelegate:self];
[myImageView addGestureRecognizer:tap];
In onImageTap method:
-(void)onImageTap:(UITapGestureRecognizer *)recognizer
{
int tag = recognizer.view.tag;
// rest of code based on image tag
}
Once you get this working and are comfortable with table views, you should look into custom table view cells by subclassing UITableViewCell and also understand table view cell reuse.
If you are feeling adventurous, I would recommend checking out UICollectionView as it will let you display two items in a row and treat each item as a different cell. You will have to manage the spacing between cells if you want strictly two items per row.
I have a horizontal UICollectionView with a UICollectionViewCell with a horizontally and vertically centered UILabel. As the label contains text of different lenght I calculate the width of the label like so. I give it a little margin (10) here to be on the save side.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [_collectionViewProvider collectionViewController:self titleForRow:indexPath.row];
CGSize size = CGSizeMake(MIN(_maxWidth, [text sizeWithFont:self.font].width + 16 + 10), CGRectGetHeight(collectionView.bounds));
return size;
}
As I like to have the users having the impression of a horizontal scroll wheel I apply a transform and adjust the zIndex of the cells depending on the distance from the center. It works almost as expected but as the frame width gets shrinked when the transform is apllied on the far left/right I have the effect that the text gets clipped which looks rather strange.
Here is an image that shows the effect and when the cell is in the center.
Has anybody an idea how to solve this?
Here is my code to calculate the layout.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *array = [super layoutAttributesForElementsInRect:rect];
CGRect visibleRect = CGRectZero;
visibleRect.origin = self.collectionView.contentOffset;
visibleRect.size = self.collectionView.bounds.size;
CGFloat activeDistance = floorf(CGRectGetWidth(visibleRect) / 2);
CGFloat midX = ceilf(CGRectGetMidX(visibleRect));
CGFloat baseAngle = M_PI/180 * 40;
for (UICollectionViewLayoutAttributes *attributes in array)
{
if (CGRectContainsRect(visibleRect, attributes.frame)) {
CGFloat distance = midX - attributes.center.x;
attributes.alpha = (activeDistance - ABS(distance))/activeDistance;
CGFloat value = -distance/activeDistance;
value *= baseAngle;
attributes.transform3D = CATransform3DMakeRotation(value, 0, 1, 0);
attributes.zIndex = ABS(distance)/activeDistance * 200;
}
else {
attributes.alpha = 0;
}
}
return array;
}
The grouped UITableView places a margin between the edge of the view and the table cells. Annoyingly (for me) this margin is some function of the width of the view.
In my application I have two UITableViews of different widths that I am looking to align the cell edges of.
Is it possible to get this margin? Or better is it possible to set this margin?
cheers,
--Ben
Grouped TableView Width in Relation to Cell Margin
TBWidth (0 to 20)
Left Margin = TBWidth - 10
TBWidth (20 to 400)
Left Margin = 10
TBWidth (401 to 546)
Left Margin = 31
TBWidth (547 to 716)
Left Margin = apx 6% of tableView
TBWidth (717 to 1024)
Left Margin = 45
- (float) groupedCellMarginWithTableWidth:(float)tableViewWidth
{
float marginWidth;
if(tableViewWidth > 20)
{
if(tableViewWidth < 400)
{
marginWidth = 10;
}
else
{
marginWidth = MAX(31, MIN(45, tableViewWidth*0.06));
}
}
else
{
marginWidth = tableViewWidth - 10;
}
return marginWidth;
}
By creating (and using!) a subclass of UITableViewCell, you can possibly achieve what you're looking for. Just move the contentview and backgroundview around in layoutsubviews, as in the code sample below which shifts the visible parts of the cell 20pt to the right.
- (void) layoutSubviews
{
NSLog (#"Cell/contentview/backgroundview before:\n%#\n%#\n%#", self, self.contentView, self.backgroundView);
[super layoutSubviews];
CGRect frame = self.backgroundView.frame;
frame.origin.x += 20;
frame.size.width -= 20;
self.backgroundView.frame = frame;
frame = self.contentView.frame;
frame.origin.x += 20;
frame.size.width -= 20;
self.contentView.frame = frame;
NSLog (#"Cell/contentview/backgroundview after:\n%#\n%#\n%#", self, self.contentView, self.backgroundView);
}
I was using Matthew Thomas implementation but it's unreliable (it's broken if you use autorotation in your controllers) and has a lot of hardcoded code... I realized that there is a very simple solution, my implementation is a single line of code:
- (float)cellMargins
{
return self.backgroundView.frame.origin.x * 2;
}
This is far better IMO :)
EDIT: my implementation was unreliable too (it does not work properly when switching to/from editing mode), this is my final implementation (I handled right and left margins separately):
- (float)leftMargin
{
return self.contentView.frame.origin.x;
}
- (float)rightMargin
{
CGRect frame = self.contentView.frame;
float containerWidth = frame.size.width;
float margin = self.frame.size.width - (containerWidth + frame.origin.x);
return margin;
}
- (float)cellMargins
{
return ([self leftMargin] + [self rightMargin]);
}
Updated Matthew Thomas's code for cell margins calculation.
Created via UITableView's category.
This can be useful, when you need to determine text height in cell, and you dont' know width of that cell.
So, actual cell can be calculated like
cell.width = tableView.width - tableView.cellsMargin * 2;
An here's the code
#implementation UITableView (CellsMargins)
// This is black magic
// from
// http://stackoverflow.com/questions/4708085/how-to-determine-margin-of-a-grouped-uitableview-or-better-how-to-set-it
- (CGFloat)cellsMargin {
// No margins for plain table views
if (self.style == UITableViewStylePlain) {
return 0;
}
// iPhone always have 10 pixels margin
if (! isIPad()) {
return 10;
}
CGFloat tableWidth = self.frame.size.width;
// Really small table
if (tableWidth <= 20) {
return tableWidth - 10;
}
// Average table size
if (tableWidth < 400) {
return 10;
}
// Big tables have complex margin's logic
// Around 6% of table width,
// 31 <= tableWidth * 0.06 <= 45
CGFloat marginWidth = tableWidth * 0.06;
marginWidth = MAX(31, MIN(45, marginWidth));
return marginWidth;
}
#end
This is now accessible through a property of UITableViewCell:
#property(nonatomic) UIEdgeInsets separatorInset
Apple Docs - UITableViewCell - seperatorInset
More accurate left margin for the 'stretchy' zone instead of the 'apx. 6% calc.'
Side note: the iPad implements additional section top and bottom padding in addition to the left margin padding, it's 10.0f below 400.0f table width and 31.0f otherwise.
-(CGFloat)leftMarginForTableView:(UITableView*)tableView
{
if (tableView.style != UITableViewStyleGrouped) return 0;
CGFloat widthTable = tableView.bounds.size.width;
if (isPhone) return (10.0f);
if (widthTable <= 400.0f) return (10.0f);
if (widthTable <= 546.0f) return (31.0f);
if (widthTable >= 720.0f) return (45.0f);
return (31.0f + ceilf((widthTable - 547.0f)/13.0f));
}
I would like to supplement Matthew's answer with a code to set-up fixed margin.
Subclass UITableViewCell.
Implement setFrame:
- (void)setFrame:(CGRect)frame
{
CGFloat inset = 15.0;
CGFloat tableWidth = 400.0;
frame.origin.x -= [self groupedCellMarginWithTableWidth:tableWidth] - inset;
frame.size.width = frame.size.width + 2 * ([self groupedCellMarginWithTableWidth:tableWidth] - inset);
[super setFrame:frame];
}
This will set left and right margin for 15.0 while table width may vary.
Just in case anyone needs this, the function for the section title left margin when obtained from tableView:titleForHeaderInSection: seems to be:
- (float)marginForSectionTitleOnGroupedTableView {
float width = self.width;
if (width <= 400) return 19;
else if (width >= 401 && width < 547) return 40;
else if (width >= 547 && width < 560) return 41;
else if (width >= 560 && width < 573) return 42;
else if (width >= 573 && width < 586) return 43;
else if (width >= 586 && width < 599) return 44;
else if (width >= 599 && width < 612) return 45;
else if (width >= 612 && width < 625) return 46;
else if (width >= 625 && width < 639) return 47;
else if (width >= 639 && width < 652) return 48;
else if (width >= 652 && width < 665) return 49;
else if (width >= 665 && width < 678) return 50;
else if (width >= 678 && width < 691) return 51;
else if (width >= 691 && width < 704) return 52;
else if (width >= 704 && width < 717) return 53;
// if (width >= 717)
return 54;
}
For widths greater than 401, the margin seems to be around 7.5% of the table view width; but when trying to align some other view with the section title, the alignment didn't work as expected; so the verbose approach seems to work better.
I have tried a different approach. But not sure whether it always work. I had a grouped UITableView and needed to make customized Header View. But as you know, when implementing viewForHeader inSection method we can not determine the margins for our created view.
So, what I have done is firstly add a CGRect property to ViewController .m file:
#property(nonatomic)CGRect groupedCellRectangle;
and in UITableViewDelegate:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
static BOOL groupFrameInitialized = NO;
if(!groupFrameInitialized){
groupFrameInitialized = YES;
self.groupedCellRectangle = cell.contentView.frame;
}
}
after then, in my viewForHeader inSection method:
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(self.groupedCellRectangle.origin.x, 0, self.groupedCellRectangle.size.width, 28)];
[v setBackgroundColor:[UIColor clearColor]];
UILabel *label = [[UILabel alloc] initWithFrame:v.frame];
label.text = #"KAF";
[v addSubview:label];
return v;
}
the result is as I assumed. groupedCellRectangle had successfully stored CGRect value with margin.
The idea behind this approach was UITableView always call viewForHeader method after willDisplay call.
Hope that helps...