A better way to implement margins in a UITableView? - ios

Apple has deprecated margins in table views, since iOS7, but designers still seem to want margins. What’s the best way t implement margins in iOS 7 and above? The old “group” table view style that included margins in iOS 6 no longer indents the left and right edges.
I have aUITableView subclass, in which i’m using the following method to programmatically add margins to the edge of all subviews, except thebackgroundView (such as cells, section header etc):
Code:
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if (subview != self.backgroundView && subview.frame.origin.x == 0.0 && subview.frame.size.width == self.frame.size.width) {
subview.frame = CGRectInset(subview.frame, 10.0, 0);
}
}
}
During runtime, it all seems to work as desired. The cells resize according to the margin offset and the subviews of those cells adjust correctly.
However, in the storyboard, usingIBDesignable, the cells adjust correctly but the cells’ subviews do not seem to resize within the cell’s calculated size
Is there a better way to implement margins in aUITableView?
Thanks c:

UIEdgeInsets is used to implement margins around the tableview that looks like grouped tableview. You may try below solutions and check it.
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if ([tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[tableView setSeparatorInset:UIEdgeInsetsZero];//iOS7
}
if ([tableView respondsToSelector:#selector(setLayoutMargins:)]) {
[tableView setLayoutMargins:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
//OR
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// Force your tableview margins (this may be a bad idea)
if ([self.tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:UIEdgeInsetsZero];
}
if ([self.tableView respondsToSelector:#selector(setLayoutMargins:)]) {
[self.tableView setLayoutMargins:UIEdgeInsetsZero];
}
}
For iOS 8 use
- (UIEdgeInsets)layoutMargins {
return UIEdgeInsetsZero;
}
Hope it helps you..

Apple switched to layout margins in iOS 8. The advantage of adding constraints to margins instead of edges, is that your margin is now adaptive, and will adjust itself, based on the screen's traits.
After I transitioned to constraining to margins (instead of edges), I really appreciated how it simplified layout, and eliminating having to adjust constraints myself. It does the work for me, and it looks pleasing.

Related

UIView's autolayout size reported from wrong size class the first time

I have a UITableViewController with some custom cells and an UIView within each cell. I have implemented the controller's cellForRowAtIndexPath to configure the UIView in each cell. For this purpose I need to know the width of the UIView on screen. Since I am using autolayout and size classes to automatically change the size of the UIView based on device orientation, I have implemented an additional method of getting the width runtime.
The problem is that when the table view is presented the first time, my code reports width for UIVIew from a compact width size class even when I am using the device in the landscape orientation. The system renders all the views as should, but my code to get the width is not working. Scrolling new cells visible or an orientation change will remedy the situation immediately.
My code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ...
CustomCell* cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = [nib firstObject];
} else {
// Clear custom content
NSArray *viewsToRemove = [cell.histogramView subviews];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
}
[cell setNeedsLayout];
[cell layoutIfNeeded];
int width = ((CustomView*)cell.customView).getWidth;
NSLog(#"width = %d", width);
// ...
}
And then:
#implementation CustomView
- (int)getWidth {
[self setNeedsLayout];
[self layoutIfNeeded];
int width = self.frame.size.width;
return width;
}
#end
Edited to add:
The problem seems to be that at when cellForRowAtIndexPath is called the first time tableview appears, autolayout has not occurred for the cell. Forcing it with [cell setNeedsLayout] and [cell layoutIfNeeded] right after creating the cell does not do the trick either.
It seems my problem root cause is a potential duplicate of How to know the width of an UITableViewCell when using auto layout? So the problem has to do with fact that when my CustomCell is loaded from a nib, it will have the default frame. Special tricks should be done to force autolayout. However, the accepted answer does not work for cells that are initially out of visible area. Any takers on this?
I would suggest trying to use constraints and ratios instead of actual width of the frame, since, as stated by Marcus Adams, the frame will have the value you are actually looking for only after viewDidAppear.
For example if your cells contain a UILabel that is 1/3 the width of the cell, and a UIImageView that fills the space left, you can set constraints between them and the parent view.
Can your logic be changed like this?
Another suggestion would be to call reloadData on your table view after viewDidAppear, but that is definitely not a nice option UX-wise.
I found out the correct solution to this:
Create a custom cell like in the original question.
Set cell.contentMode = UIViewContentModeRedraw in tableview: cellForRowAtIndexPath:
Implement layoutSubviews for the custom cell like so:
- (void)layoutSubviews {
[super layoutSubviews];
NSLog(#"cell width == %f", self.bounds.size.width);
}
This way you will have access to the size of the cell as it will appear on screen.

How to remove separator inset from UITableView in iPad (iOS 9 and above)?

I knew this question is old, so many answers was already posted here.
The problem is, the solutions are working fine with iPhone's. But when trying it with iPad, I still faces some issue.
Refer the below images(First one is portrait mode and the second one is in landscape mode).
I used the below code in my view controller where I wrote table view's delegate methods.
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if ([tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[tableView setSeparatorInset:UIEdgeInsetsZero];
}
if ([tableView respondsToSelector:#selector(setLayoutMargins:)]) {
[tableView setLayoutMargins:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
Question:
Is there is any way to remove these left empty space of table view's separator in iPad? Should I include any extra check's beyond the iPhone's working code?
Confused!!
You may need to set the Table View's
cellLayoutMarginsFollowReadableWidth to NO if you want to customize
insets or margins. Your mileage may vary, this is not documented very
well.
This property only exists in iOS 9 so be sure to check before setting.
if([myTableView respondsToSelector:#selector(setCellLayoutMarginsFollowReadableWidth:)])
{
myTableView.cellLayoutMarginsFollowReadableWidth = NO;
}

UITableViewHeaderFooterView and Interface Builder: how to adjust width for different devices?

I am implementing a custom Header view for my UITableView. I created a HeaderView class that extends UITableViewHeaderFooterView and a Nib file for this view.
In the view controller, I registered it:
[self.tableView registerNib:[UINib nibWithNibName:#"HeaderView" bundle:[NSBundle mainBundle]] forHeaderFooterViewReuseIdentifier:#"HeaderView"];
And then set as a header:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
HeaderView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"HeaderView"];
return view;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 26.0f;
}
The problem is that the width of the view is always the same as in the nib file. Since now there are multiple screen widths, I cannot simply set the view width to 320 (or the width of the table view) in the nib file.
How exactly am I supposed to set up the header view so it automatically fits the table width?
Update: here's a comparison when I change the view simulated metrics from Inferred to a fixed one (iPhone 3.5" in this case):
img http://cl.ly/image/451n2N303J3S/Image%202015-04-19%20at%206.39.58%20PM.png
Had the same problem ... the only solution I found was by setting all the frames of the subviews in my custom UITableViewHeaderFooterView to the according state.
Dirty example:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
NSString *headerIdentifier = #"SectionHeader";
TableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerIdentifier];
if (headerView == nil) {
headerView = [[TableViewHeaderFooterView alloc] initWithReuseIdentifier:headerIdentifier];
}
.....
[headerView setWidthForSubviews:tableView.frame.size.width]; // set the right width of the right subviews
....
return headerView;
}
in TableViewHeaderFooterView
- (void)setWidthForSubviews:(CGFloat)width {
for (UIView *view in #[[self viewWithTag:1],[self viewWithTag:2],[self viewWithTag:3], ...]) {
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, width, view.frame.size.height);
}
}
If someone finds another solution I'll be grateful but it seems that UITableViewHeaderFooterView may have problems with autolayout, especially if you load it from a nib.
I would look at a couple of things.
If you using springs and struts, then trying set the autoResizingMask
[maskingBackground setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
If using autolayout then pin to the tableWidth and use a constant height constraint.
If that does not meet your needs I would look at using this in your header view class
-(void)willMoveToSuperview:(UIView*)superview
And setting your frame in there using the superview.frame.size to guide you.

UITableview SepartorInset full width issue on my xcode6

I am using xcode6.0.1.In my UIVIew Controller contains one tableview and i need to set separator inset full width.I changed my tableview separator inset via xib.But i can’t get full width for separator inset on my xcode6 (In my xcode5 separator inset full width is possible).How to solve this issue?
change tableview separator inset via xib
my tableview separator line like this(not get full width)
I solved my issue
-(void)viewDidLoad{
self.tableView.layoutMargins = UIEdgeInsetsZero;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
Use this method in iOS 8 [tableView setLayoutMargins:UIEdgeInsetsZero];
I guess you simulated your app in iOS8 , this is a new property "#property(nonatomic) UIEdgeInsets layoutMargins " in iOS8. I suggest you try to create a UITableViewCell class category(e.g. UITableViewCell+Separator) then add this getter
- (UIEdgeInsets)layoutMargins
{
return UIEdgeInsetsZero;
}
in iOS7 this will not be called cos there's no this property in SDK,and will not cause any crash; in iOS8 this will be called every time you use the cell
It works for me

Is there a way to make UITableView cells in iOS 7 not have a line break in the separator?

I noticed that in iOS 7, UITableViewCells have a line break in the separator of the cell that iOS 6 does not have. Is there a way to get rid of this line break? Changing the separator to none and then making UIViews with the color of the separator still causes the white separator to occur regardless.
For iOS7:
if ([self.tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:UIEdgeInsetsZero];
}
For iOS8:
First configure your table view as follows:
if ([self.tableView respondsToSelector:#selector(layoutMargins)]) {
self.tableView.layoutMargins = UIEdgeInsetsZero;
}
Then in your cellForRowAtIndexPath: method, configure the cell as follows:
if ([cell respondsToSelector:#selector(layoutMargins)]) {
cell.layoutMargins = UIEdgeInsetsZero;
}
Note: Include both layoutMargins and separatorInset, to support both iOS versions
You can also set the 'Separator Inset' from the storyboard:
If you want to support older versions of iOS, you should check for the availability of this method before calling it:
if ([self.tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:UIEdgeInsetsZero];
}
Figured it out.
[self.tableView setSeparatorInset:UIEdgeInsetsMake(0, 0, 0, 0)];
The selected answer did not work for me. But this yes and it works from ios 2.0:
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
Swift version
iOS introduces the layoutMargins property on cells AND table views.
This property isn't available in iOS 7.0 so you need to make sure you check before assigning it!
However, Apple has added a **property to your cell that will prevent it from inheriting your Table View's margin settings. This way, your cells can configure their own margins independently of the table view. Think of it as an override.
This property is called preservesSuperviewLayoutMargins, and setting it to NO can allow you to override your Table View's layoutMargin settings with your own cell's layoutMargin setting. It both saves time (you don't have to modify the Table View's settings), and is more concise. Please refer to Mike Abdullah's answer for a detailed explanation.
NOTE: this is the proper, less messy implementation, as expressed in Mike Abdullah's answer; setting your cell's preservesSuperviewLayoutMargins=NO will ensure that your Table View does not override the cell settings.
First step - Setup your cell margins:
/*
Tells the delegate the table view is about to draw a cell for a particular row.
*/
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath)
{
// Remove seperator inset
if cell.respondsToSelector("setSeparatorInset:") {
cell.separatorInset = UIEdgeInsetsZero
}
// Prevent the cell from inheriting the Table View's margin settings
if cell.respondsToSelector("setPreservesSuperviewLayoutMargins:") {
cell.preservesSuperviewLayoutMargins = false
}
// Explictly set your cell's layout margins
if cell.respondsToSelector("setLayoutMargins:") {
cell.layoutMargins = UIEdgeInsetsZero
}
}
Setting the preservesSuperviewLayoutMargins property on your cell to NO should prevent your table view from overriding your cell margins. In some cases, it seems to not function properly.
Second step - Only if all fails, you may brute-force your Table View margins:
/*
Called to notify the view controller that its view has just laid out its subviews.
*/
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Force your tableview margins (this may be a bad idea)
if self.tableView.respondsToSelector("setSeparatorInset:") {
self.tableView.separatorInset = UIEdgeInsetsZero
}
if self.tableView.respondsToSelector("setLayoutMargins:") {
self.tableView.layoutMargins = UIEdgeInsetsZero
}
}
...and there you go! This should work on iOS 8 as well as iOS 7.
Note: tested using iOS 8.1 and 7.1, in my case I only needed to use The First Step of this explanation.
The Second Step is only required if you have unpopulated cell beneath the rendered cells, ie. if the table is larger than the number of rows in the table model. Not doing the second step would result in different separator offsets.
For a permanent solution app-wide, call this in application:didFinishLaunching .. any tableview will be "fixed"
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
[[UITableView appearance]setSeparatorInset:UIEdgeInsetsZero];
}
And in iOS 8, based on the Seth McFarlane post here, you need to start dealing with layoutMargins. The following did it for me:
In ApplicationDidFinishLaunching, run the following:
if ([UITableViewCell instancesRespondToSelector:#selector(setLayoutMargins:)]) {
[[UITableViewCell appearance]setLayoutMargins:UIEdgeInsetsZero];
}
Create the following category on UITableView:
#interface UITableView(WJAdditions)
-(void) wjZeroSeparatorInset;
#end
#implementation UITableView (WJAdditions)
-(void) wjZeroSeparatorInset {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if ([self respondsToSelector:#selector(setLayoutMargins:)]) {
self.layoutMargins = UIEdgeInsetsZero;
}
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
if ([self respondsToSelector: #selector(setSeparatorInset:)])
self.separatorInset = UIEdgeInsetsZero;
}
#endif
}
#end
In your UITableViews, shortly after initializing, call
[self wjZeroSeparatorInset];
In your UITableViewControllers, you need the following:
-(void) viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
UITableView* tv = self.tableView;
if ([tv respondsToSelector:#selector(setLayoutMargins:)]) {
[tv setLayoutMargins:UIEdgeInsetsZero];
}
}
In iOS 8, there appears to be a small bug. The custom value for separator inset doesn't get applied to the cells with text in it. I tried setting it from the interface builder and through code but neither worked. I've filed a bug report as well.
In the meantime, I have a small workaround to achieve this. I simply draw the line on the cell. Create a subclass of UITableViewCell and override the drawRect method. Also don't forget to set the separator property of the UITableView to None. Here is the code. It's in Swift but since its basically C, you can use the same thing in Objective-C as well.
override func drawRect(rect: CGRect) {
super.drawRect(rect)
let context = UIGraphicsGetCurrentContext()
CGContextSetStrokeColorWithColor(context, UITableView().separatorColor.CGColor) // seperator color
CGContextSetLineWidth(context, 2) // separator width
CGContextMoveToPoint(context, 0, self.bounds.size.height)
CGContextAddLineToPoint(context, self.bounds.size.width, self.bounds.size.height)
CGContextStrokePath(context)
}
Reset separatorInset fixes this problem for me:
if ([self.tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:self.tableView.separatorInset];
}
The solution:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if ([tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[tableView setSeparatorInset:UIEdgeInsetsZero];
}
if ([tableView respondsToSelector:#selector(setLayoutMargins:)]) {
[tableView setLayoutMargins:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
Beware as setting the separatorInset of tableView to UIEdgeInsetsZero seems to break the left margin of section titles at the same time! (at least it does on iOS 7.1)
If you want to display section titles with tableView:titleForHeaderInSection: and still get the desire effect of no line break between UITableViewCells, you must set the separatorInset directly on the cells instead of the tableView!
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ...
[cell setSeparatorInset:UIEdgeInsetsZero];
For iOS8+ this worked for me,
tableView.layoutMargins = UIEdgeInsetsZero
and in cellForRowAtIndexPath method:
cell.layoutMargins = UIEdgeInsetsZero;
- (void)viewDidLoad
{
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
[super viewDidLoad];
// Additional setup
}
Note: the setSeparatorStyle line has to be above the super call.
#implementation UITableView (HGApperance)
-(void)setSeparatorXMargin:(CGFloat)margin{
// iOS 7
if ([self respondsToSelector:#selector(setSeparatorInset:)]) {
[self setSeparatorInset:UIEdgeInsetsZero];
}
if ([self respondsToSelector:#selector(layoutMargins)]) {
self.layoutMargins = UIEdgeInsetsZero;
}
}
#end
For TableviewCell, Willam's answer works perfect.
To iOS8 and above
If you find that all the solutions don't work like me, I think you may have used the subclass of UITableViewCell and override the layoutSubviews method, if you meet two the conditions, go on reading.
Do or check it step by step.
1、After alloc and init the UITableView, set its layoutMargins property.
if ([_tableView respondsToSelector:#selector(setLayoutMargins:)]) {
_tableView.layoutMargins = UIEdgeInsetsZero ;
}
2、In the method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath, after alloc and init your custom UITableViewCell, set its layoutMargins property.
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
cell.layoutMargins = UIEdgeInsetsZero ;
}
3、The most important part comes in. If you override the - (void)layoutSubviews method of UITableViewCell, don't forget to call its super.
- (void)layoutSubviews
{
[super layoutSubviews] ;
// layout the subviews
}
Xamarin.iOS Version
I extended the UITableView separator the full width in Xamarin.iOS on iOS 8.4 with help from posts by Luis E. Prado and King-Wizard. I had to set both SeparatorInset and LayoutMargins to get it working. I did not write checks for lower iOS versions since I am targeting iOS 8 and above.
I wrote the following two C# extension methods to set the UITableView separator full width at the UITableView or UITableViewCell level. You might use one or both of these methods depending on the desired effect (see the explanations below).
UITableView
Set the UITableView properties to alter the separators that appear after empty cells. This SetLayoutMarginsZero method could be called from the related ViewController's ViewDidLoad method.
public static void SetLayoutMarginsZero(this UITableView tableView)
{
tableView.SeparatorInset = UIEdgeInsets.Zero;
tableView.LayoutMargins = UIEdgeInsets.Zero;
}
UITableViewCell
Set the UITableViewCell properties to alter the separators that appear after populated cells. This SetLayoutMarginsZero method could be called from the TableViewSource GetCell method.
public static void SetLayoutMarginsZero(this UITableViewCell tableViewCell)
{
tableViewCell.SeparatorInset = UIEdgeInsets.Zero;
tableViewCell.LayoutMargins = UIEdgeInsets.Zero;
tableViewCell.PreservesSuperviewLayoutMargins = false;
}

Resources