Dynamic heightForHeaderInSection - header

How can I modify the height of a header section accordantly to the view I'm adding it?
heightForHeaderInSection is called before viewForHeaderInSection and I don't know the view size until I create it.

You can follow this:
Create a property for headerView (NSArray if multiple header views).
Create the view in "heightForHeaderInSection" itself.
Assign your view to property.
return the height of your view in "heightForHeaderInSection".
In "viewForHeaderInSection" return this property.

Yeah, it seems strange/redundant that you have to create both the view and then also determine the size of that view as a completely separate event, but you can minimize the silliness by moving some of that shared logic into some shared method. E.g., you probably have to figure out the size of the view at some point during it's creation, so move that logic to some shared method.
For example, I have logic that I use for determining the size of the UILabel I'm putting in my header based upon the size of the text. So I pulled that out of my viewForHeaderInSection and moved it into my own method, sizeForHeaderLabelInSection, which I use to determine the size of my label control):
- (CGSize)tableView:(UITableView *)tableView sizeForHeaderLabelInSection:(NSInteger)section
{
NSString *text = [self tableView:tableView titleForHeaderInSection:section];
CGSize constraint = CGSizeMake(self.view.frame.size.width - kSectionTitleLeftMargin - kSectionTitleRightMargin, kMaxSectionTitleHeight);
return [text sizeWithFont:[self fontForSectionHeader] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
}
Then, I modified the standard heightForHeaderInSection to use that method, adding, of course, my top and bottom margin around my UILabel:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return [self tableView:tableView sizeForHeaderLabelInSection:section].height + kSectionTitleTopMargin + kSectionTitleBottomMargin;
}
And then I modified the standard viewForHeaderInSection to also use this sizeForHeaderLabelInSection, too:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
CGSize size = [self tableView:tableView sizeForHeaderLabelInSection:section];
UIView* headerView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, size.width + kSectionTitleLeftMargin + kSectionTitleRightMargin, size.height + kSectionTitleTopMargin + kSectionTitleBottomMargin)];
//headerView.contentMode = UIViewContentModeScaleToFill;
// Add the label
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(kSectionTitleLeftMargin,
kSectionTitleTopMargin,
size.width,
size.height)];
// put stuff to set up my headerLabel here...
[headerView addSubview:headerLabel];
// Return the headerView
return headerView;
}
Clearly, how you do this is completely up to you and what you're trying to achieve. But I think you'll have success if you shift your mindset from "how do I figure out the size of that view I created in viewForHeaderInSection" to "how can I move the the code that I used for determining the size in viewForHeaderInSection into some common method that my heightForSectionInHeader can use, too.

This also works:
Override estimatedHeightForHeaderInSection, return a CGFloat, whatever, but not 0.
Create a CGFloat property to save the height for header view.
var sectionHeaderViewHeight:CGFloat = 10.0
(The value is not important, but never set to 0.0, if you do this, viewForHeaderInSection will never be invoked, you will never have a section header view!)
Override heightForHeaderInSection and return that property.
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return sectionHeaderViewHeight
}
Create the header view in viewForHeaderInSection
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
//configuration your view
//.....
return headerView
}
This is the key step: Add these code before return headerView.
self.tableView.beginUpdates()
sectionHeaderViewHeight = //The value you would like to set
self.tableView.endUpdates()
It looks like this:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
//configuration your view
//.....
//----Key Steps:----
self.tableView.beginUpdates()
sectionHeaderViewHeight = //The value you would like to set
self.tableView.endUpdates()
//----Key Steps----
return headerView
}
var sectionHeaderViewHeight:CGFloat = 10.0
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return sectionHeaderViewHeight
}

To force calling heightForHeaderInSection method, you just need to call both methods beginUpdates/endUpdates of the UITableView.
First create and init a variable height and return it
var height: CGFloat = 160.0
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return height
}
Then add a method that will update the section height with a new value
func updateHeaderHeight(newHeight: CGFloat) {
tableView.beginUpdates()
height = newHeight
tableView.endUpdates()
}

Related

How to figure out if tableViews footer is displayed?

I have a tableView and I want to know when the footer will be displayed
I used the scrollView to figure out the end of tableView.
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let height = scrollView.frame.size.height
let contentYoffset = scrollView.contentOffset.y
let distanceFromBottom = scrollView.contentSize.height - contentYoffset
if distanceFromBottom < height {
// end of tableView
}
}
and I also used Bool to understand when was the first time the footer was displayed
UITableViewDelegate has
#available(iOS 6.0, *)
optional func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int)
method to get this

NSTableRowView/NSTableCellView how to set custom color to selected row?

I am trying to implement custom row color when table row is selected.
-(void)tableViewSelectionDidChange:(NSNotification *)notification{
NSInteger selectedRow = [_mainTable selectedRow];
NSTableCellView *cell = [_mainTable rowViewAtRow:selectedRow makeIfNecessary:NO];
cell.layer.backgroundColor = [NSColor redColor].CGColor;
NSLog(#"selected");
}
But this is not working. I find that Apple documentation very confusing (maybe I am wrong). I am not experienced with Mac programming.
Can someone suggest any solution? Basically I need that selection Color to be transparent.
Solution
This should be done by subclassing NSTableRowView and then returning your subclass in with the NSTableView delegate method
-(NSTableRowView*)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
Subclassing NSTableRowView provides much more flexibility when modifying your row view. Returning your subclass in the NSTableView delegate method above
will also automatically remove the background selection color when clicking from one row to the next (which is an open issue in the other answer provided).
Steps
First, subclass NSTableRowView and override drawSelectionInRect to change its background color when selected:
#implementation MyTableRowView
- (void)drawSelectionInRect:(NSRect)dirtyRect
{
[super drawSelectionInRect:dirtyRect];
[[NSColor yellowColor] setFill];
NSRectFill(dirtyRect);
}
Next, return your subclassed row view using the rowViewForRow NSTableView delegate method:
- (NSTableRowView*)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
{
static NSString* const kRowIdentifier = #"MyTableRow";
MyTableRowView* myRowView = [tableView makeViewWithIdentifier:kRowIdentifier owner:self];
if (!myRowView) {
myRowView = [[MyTableRowView alloc] initWithFrame:NSZeroRect];
myRowView.identifier = kRowIdentifier;
}
return rowView;
}
Using this approach, you can also easily override other elements like the separator color. To do this, override the drawSeparatorInRect method in your NSTableRowView subclass like so:
- (void)drawSeparatorInRect:(NSRect)dirtyRect
{
// Change the separator color if the row is selected
if (self.isSelected) [[NSColor orangeColor] setFill];
else [[NSColor grayColor] setFill];
// Fill the seperator
dirtyRect.origin.y = dirtyRect.size.height - 1.0;
dirtyRect.size.height = 1.0;
NSRectFill(dirtyRect);
}
Resources
Overriding NSTableRowView display settings
https://developer.apple.com/reference/appkit/nstablerowview
NSTableview rowViewForRow delegate method
https://developer.apple.com/reference/appkit/nstableviewdelegate/1532417-tableview
first set tableview selection highlight style to
NSTableViewSelectionHighlightStyleNone
then in your tablView delegate implement
tableView:shouldSelectRow:
and write this code inside it:
NSTableViewRow *row= [_mainTable rowViewAtRow:selectedRow makeIfNecessary:NO];
row.backgroundColor = [your color];
return YES;
read these also
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSTableViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/NSTableViewDelegate/tableView:rowViewForRow:
for selection style
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSTableView_Class/index.html#//apple_ref/occ/instp/NSTableView/selectionHighlightStyle
This is to set the custom color to the selected row and also the highlighted text color. The output should look something like this,
In the above screenshot, we are doing
Setting the background selected color to white
Adding the corner radius
Changing the text color to blue
Adding the blue stroke color
You can do a lot more customization but this answer covers above-mentioned points.
1. Start with subclassing NSTableRowView
class CategoryTableRowView: NSTableRowView {
override func drawSelection(in dirtyRect: NSRect) {
if selectionHighlightStyle != .none {
let selectionRect = bounds.insetBy(dx: 2.5, dy: 2.5)
NSColor(calibratedRed: 61.0/255.0, green: 159.0/255.0, blue: 219.0/255.0, alpha: 1.0).setStroke()
NSColor(calibratedWhite: 1.0, alpha: 1.0).setFill()
let selectionPath = NSBezierPath(roundedRect: selectionRect, xRadius: 25, yRadius: 25)
selectionPath.fill()
selectionPath.stroke()
}
}
}
2. Return custom CategoryTableRowView() in the NSTableViewDelegate method
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
return CategoryTableRowView()
}
3. Make sure you have selectionHighlightStyle to regular in your ViewController class
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.selectionHighlightStyle = .regular
}
4. To set the textColor, create a subclass of NSTableCellView
class CategoryCellView: NSTableCellView {
#IBOutlet weak var categoryTextField: NSTextField!
override var backgroundStyle: NSView.BackgroundStyle {
willSet{
if newValue == .dark {
categoryTextField.textColor = NSColor(calibratedRed: 61.0/255.0, green: 159.0/255.0, blue: 219.0/255.0, alpha: 1.0)
} else {
categoryTextField.textColor = NSColor.black
}
}
}
}
override the backgroundStyle property and set the desired color for the text.
Note: In my case, I have a custom cell which has a categoryTextField outlet.So to set the text color I use:
categoryTextField.textColor = NSColor.black
5. Set custom class inside storyboard
I hope this helps. Thanks.

Dynamically size uitableViewCell according to UILabel (With paragraph spacing)

I have a UITableView which is populated by text and images from a JSON file. The TableView Cell is currently sizing correctly for "posts" that do not contain many line breaks in the text however I cannot get it to calculate the correct height for "posts" with 4 or 5 line breaks.
Code for getting height:
-(float)height :(NSMutableAttributedString*)string
{
NSString *stringToSize = [NSString stringWithFormat:#"%#", string];
CGSize constraint = CGSizeMake(LABEL_WIDTH - (LABEL_MARGIN *2), 2000.f);
CGSize size = [stringToSize sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:contraint lineBreakMode:NSLineBreakByWordWrapping];
return size.height;
}
How do I calculate the correct size while allowing for line breaks and white space?
EDIT
The Rest of the method,
Inside of TableView CellForRow:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *row = [NSString stringWithFormat:#"%i", indexPath.row];
float postTextHeight = [self height:postText];
NSString *height = [NSString stringWithFormat:#"%f", heightOfPostText + 70];
[_cellSizes setObject:height forKey:row];
}
And the height of Table Cell:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *imageHeightString = [NSString stringWithFormat:#"%#", [_cellSizes objectForKey:indexPath.row]];
float heightOfCell = [imageHeightString floatValue];
if (heightOfCell == 0) {
return 217;
};
return heightOfCell + 5;
}
better u need to calculate the height first, don't include the height calculation part in method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Better to calculate it in method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
since u are getting the data from json it is easy for u to calculate
in the "heightForRowAtIndexPath" method.
follwing code will give the example to calculate height of text change it ur requirement.
hopee this helps u :)
// i am using an array
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIFont *labelFont = [UIFont fontWithName:#"Noteworthy-Bold" size:20];
NSDictionary *arialdict = [NSDictionary dictionaryWithObject:labelFont forKey:NSFontAttributeName];
NSMutableAttributedString *message = [[NSMutableAttributedString alloc] initWithString:#"this is just the sample example of how to calculate the dynamic height for tableview cell which is of around 7 to 8 lines. you will need to set the height of this string first, not seems to be calculated in cellForRowAtIndexPath method." attributes:arialdict];
array = [NSMutableArray arrayWithObjects:message, nil];
NSMutableAttributedString *message_1 = [[NSMutableAttributedString alloc] initWithString:#"you will need to set the height of this string first, not seems to be calculated in cellForRowAtIndexPath method." attributes:arialdict];
[array addObject:message_1];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *Cell = [self.aTableView dequeueReusableCellWithIdentifier:#"cell"];
if(Cell == nil)
{
Cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
//dont include the height calculation part hear, becz heights are already set for all the cell
[Cell.textLabel sizeToFit];
Cell.textLabel.attributedText = [array objectAtIndex:indexPath.row]; // dont calculate height hear it will be called after "heightForRowAtIndexPath" method
Cell.textLabel.numberOfLines = 8;
return Cell;
}
// put ur height calculation method i took some hardcoded values change it :)
-(float)height :(NSMutableAttributedString*)string
{
/*
NSString *stringToSize = [NSString stringWithFormat:#"%#", string];
// CGSize constraint = CGSizeMake(LABEL_WIDTH - (LABEL_MARGIN *2), 2000.f);
CGSize maxSize = CGSizeMake(280, MAXFLOAT);//set max height //set the constant width, hear MAXFLOAT gives the maximum height
CGSize size = [stringToSize sizeWithFont:[UIFont systemFontOfSize:20.0f] constrainedToSize:maxSize lineBreakMode:NSLineBreakByWordWrapping];
return size.height; //finally u get the correct height
*/
//commenting the above code because "sizeWithFont: constrainedToSize:maxSize: lineBreakMode: " has been deprecated to avoid above code use below
NSAttributedString *attributedText = string;
CGRect rect = [attributedText boundingRectWithSize:(CGSize){225, MAXFLOAT}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];//you need to specify the some width, height will be calculated
CGSize requiredSize = rect.size;
return requiredSize.height; //finally u return your height
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//whatever the height u need to calculate calculate hear only
CGFloat heightOfcell = [self height:[array objectAtIndex:indexPath.row]];
NSLog(#"%f",heightOfcell);
return heightOfcell;
}
Hope this helps u :)
For SWIFT version
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate
{
var messageArray:[String] = [] //array to holde the response form son for example
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
messageArray = ["One of the most interesting features of Newsstand is that once an asset downloading has started it will continue even if the application is suspended (that is: not running but still in memory) or it is terminated. Of course during while your app is suspended it will not receive any status update but it will be woken up in the background",
"In case that app has been terminated while downloading was in progress, the situation is different. Infact in the event of a finished downloading the app can not be simply woken up and the connection delegate finish download method called, as when an app is terminated its App delegate object doesn’t exist anymore. In such case the system will relaunch the app in the background.",
" If defined, this key will contain the array of all asset identifiers that caused the launch. From my tests it doesn’t seem this check is really required if you reconnect the pending downloading as explained in the next paragraph.",
]
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return messageArray.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("CELL") as? UITableViewCell;
if(cell == nil)
{
cell = UITableViewCell(style:UITableViewCellStyle.Default, reuseIdentifier: "CELL")
cell?.selectionStyle = UITableViewCellSelectionStyle.None
}
cell?.textLabel.font = UIFont.systemFontOfSize(15.0)
cell?.textLabel.sizeToFit()
cell?.textLabel.text = messageArray[indexPath.row]
cell?.textLabel.numberOfLines = 0
return cell!;
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
var height:CGFloat = self.calculateHeightForString(messageArray[indexPath.row])
return height + 70.0
}
func calculateHeightForString(inString:String) -> CGFloat
{
var messageString = inString
var attributes = [UIFont(): UIFont.systemFontOfSize(15.0)]
var attrString:NSAttributedString? = NSAttributedString(string: messageString, attributes: attributes)
var rect:CGRect = attrString!.boundingRectWithSize(CGSizeMake(300.0,CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil )
var requredSize:CGRect = rect
return requredSize.height //to include button's in your tableview
}
}
#Shan had a good answer but it didn't entirely worked for me.
This is the code I used for calculating the cell height
-(float)height :(NSMutableAttributedString*)string
{
CGRect rect = [string boundingRectWithSize:(CGSize){table.frame.size.width - 110, MAXFLOAT} options:NSStringDrawingUsesLineFragmentOrigin context:nil];
return rect.size.height;
}
I do the -110 because that will give equal space at sides top and bottom.
Hope this helps.
Implement this table view delegate method:
-tableView:heightForRowAtIndexPath:
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:
You'll call your method for determining the height and return that value with some extra padding if you wanted.
The approach I recommend is to set the text of an actual label and get the required height by calling sizeToFit. For this to work, you've got to set the label's numberOfLines property to 0 and set the desired maximum width.
When using this technique with table views, you can use the prototype cell method discussed here to calculate height using an actual cell.

How to remove the blank space at the top of a grouped UITableView?

When you create a UITableView with the UITableViewStyleGrouped style, it adds quite a lot of space in between the actual tableviewcells and the borders of the table's frame. This space is above, below, to the left, and to the right of the tableviewcells.
You can change the space between tableviewcells themselves by:
[myTableView setSectionHeaderHeight:0];
[myTableView setSectionFooterHeight:25];
However, even by doing that, there's still this annoying space between the top of the frame and the first tableviewcell. Furthermore, there's a ton of space still in between the left of the frame and the tableviewcell, as well as the right side of the frame and the tableviewcell.
-=-=-=-=-=-=-
Is there a way to manipulate that space (resize it)? My only solution thus far is to make the size of the frame larger than the screen to fake out the program into having that "blank space" outside of the screen, thus removing it. However, this is obviously not optimal and a clear hack.
The space is there because of the UITableView's tableHeaderView property. When the the tableHeaderView property is nil Apple defaults a view. So the way around this is to create an empty view with a height greater than 0. Setting this overrides the default view thereby removing the unwanted space.
This can be done in a Storyboard by dragging a view to the top of a tableView and then setting the height of the view to a value of 1 or greater.
Or it can be done programmatically with the following code:
Objective-C:
CGRect frame = CGRectZero;
frame.size.height = CGFLOAT_MIN;
[self.tableView setTableHeaderView:[[UIView alloc] initWithFrame:frame]];
Swift:
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
tableView.tableHeaderView = UIView(frame: frame)
Comments
As others have noted you can use this same solution for footers.
Sources and Acknowledgements
See the Documentation for more details on the tableHeaderView property.
Thanks to #liushuaikobe for verifying using the least positive normal number works.
Answer in Swift 4
If the table view is selected in interface builder and in the attributes inspector the style "Grouped" is selected, enter the following code in your view controller to fix the extra header space issue.
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude
}
Use it in the viewDidLoad() method.
tableView.tableHeaderView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: Double.leastNormalMagnitude))
Single line solution:
Objective-C
self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, CGFLOAT_MIN)];
Swift
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: Double(FLT_MIN)))
Follow these steps to save your day.
Select grouped attributes from the storyboard.
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude
}
If you are using ios 15 do this also —
if #available(iOS 15.0, *){
self.tableViewSavedRecent.sectionHeaderTopPadding = 0.0
}
For iOS 11.0+
tableView.contentInsetAdjustmentBehavior = .never
If the style of your tableView is UITableViewStyleGrouped, then you have to pay attention to the delegate of the height of SectionHeader or SectionFooter, cause this needs to be implemented right under this case.
The return value should not be 0, even if the SectionHeader or the height of SectionFooter is 0, it needs to be a very small value; try CGFLOAT_MIN.
For my example:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
if (section == [self.dataArray indexOfObject:self.bannerList]) {
return 46;
}
return CGFLOAT_MIN;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return CGFLOAT_MIN;
}
Make sure you implemented these two methods, and the value is right, and the top margin will be fixed.
You need to set footer too
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
return 0;
}
-(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
return [[UIView alloc] initWithFrame:CGRectZero];
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude
}
Below location icon is table with top spacing zero.
I had to combine both delegate functions for it to work:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return .leastNonzeroMagnitude
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: .leastNonzeroMagnitude))
}
None of above solution worked for me
In my case these setting was set to manual i just changed to Automatic
If you are using a TableHeaderView with an insetGroup styled UITableView you can do the following:
class CustomTableHeaderView: UIView {
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
--- do your header view setup here. ---
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Why do all backgrounds disappear on UITableViewCell select?

My current project's UITableViewCell behavior is baffling me. I have a fairly straightforward subclass of UITableViewCell. It adds a few extra elements to the base view (via [self.contentView addSubview:...] and sets background colors on the elements to have them look like black and grey rectangular boxes.
Because the background of the entire table has this concrete-like texture image, each cell's background needs to be transparent, even when selected, but in that case it should darken a bit. I've set a custom semi-transparent selected background to achieve this effect:
UIView *background = [[[UIView alloc] initWithFrame:self.bounds] autorelease];
background.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
background.opaque = NO;
[self setSelectedBackgroundView:background];
And although that yields the right look for the background, a weird side effect happens when I select the cell; all other backgrounds are somehow turnt off. Here's a screenshot. The bottom cell looks like it should and is not selected. The top cell is selected, but it should display the black and grey rectangular areas, yet they are gone!
Who knows what's going on here and even more important: how can I correct this?
What is happening is that each subview inside the TableViewCell will receive the setSelected and setHighlighted methods. The setSelected method will remove background colors but if you set it for the selected state it will be corrected.
For example if those are UILabels added as subviews in your customized cell, then you can add this to the setSelected method of your TableViewCell implementation code:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
self.textLabel.backgroundColor = [UIColor blackColor];
}
where self.textLabel would be whatever those labels are that are shown in the picture above
I'm not sure where your adding your selected view, I usually add it in the setSelected method.
Alternatively, you can subclass the UILabel and override the setHighlighted method like so:
-(void)setHighlighted:(BOOL)highlighted
{
[self setBackgroundColor:[UIColor blackColor]];
}
The cell highlighting process can seem complex and confusing if you don't know whats going on. I was thoroughly confused and did some extensive experimentation. Here's the notes on my findings that may help somebody (if anyone has anything to add to this or refute then please comment and I will endeavour to confirm and update)
In the normal “not selected” state
The contentView (whats in your XIB unless you coded it otherwise) is drawn normally
The selectedBackgroundView is HIDDEN
The backgroundView is visible (so provided your contentView is transparent you see the backgroundView or (if you have not defined a backgroundView you'll see the background colour of the UITableView itself)
A cell is selected, the following occurs immediately with-OUT any animation:
All views/subviews within the contentView have their backgroundColor cleared (or set to transparent), label etc text color's change to their selected colour
The selectedBackgroundView becomes visible (this view is always the full size of the cell (a custom frame is ignored, use a subview if you need to). Also note the backgroundColor of subViews are not displayed for some reason, perhaps they're set transparent like the contentView). If you didn't define a selectedBackgroundView then Cocoa will create/insert the blue (or gray) gradient background and display this for you)
The backgroundView is unchanged
When the cell is deselected, an animation to remove the highlighting starts:
The selectedBackgroundView alpha property is animated from 1.0 (fully opaque) to 0.0 (fully transparent).
The backgroundView is again unchanged (so the animation looks like a crossfade between selectedBackgroundView and backgroundView)
ONLY ONCE the animation has finished does the contentView get redrawn in the "not-selected" state and its subview backgroundColor's become visible again (this can cause your animation to look horrible so it is advisable that you don't use UIView.backgroundColor in your contentView)
CONCLUSIONS:
If you need a backgroundColor to persist through out the highlight animation, don't use the backgroundColor property of UIView instead you can try (probably with-in tableview:cellForRowAtIndexPath:):
A CALayer with a background color:
UIColor *bgColor = [UIColor greenColor];
CALayer* layer = [CALayer layer];
layer.frame = viewThatRequiresBGColor.bounds;
layer.backgroundColor = bgColor.CGColor;
[cell.viewThatRequiresBGColor.layer addSublayer:layer];
or a CAGradientLayer:
UIColor *startColor = [UIColor redColor];
UIColor *endColor = [UIColor purpleColor];
CAGradientLayer* gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = viewThatRequiresBGColor.bounds;
gradientLayer.colors = #[(id)startColor.CGColor, (id)endColor.CGColor];
gradientLayer.locations = #[[NSNumber numberWithFloat:0],[NSNumber numberWithFloat:1]];
[cell.viewThatRequiresBGColor.layer addSublayer:gradientLayer];
I've also used a CALayer.border technique to provide a custom UITableView seperator:
// We have to use the borderColor/Width as opposed to just setting the
// backgroundColor else the view becomes transparent and disappears during
// the cell's selected/highlighted animation
UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake(0, 43, 1024, 1)];
separatorView.layer.borderColor = [UIColor redColor].CGColor;
separatorView.layer.borderWidth = 1.0;
[cell.contentView addSubview:separatorView];
When you start dragging a UITableViewCell, it calls setBackgroundColor: on its subviews with a 0-alpha color. I worked around this by subclassing UIView and overriding setBackgroundColor: to ignore requests with 0-alpha colors. It feels hacky, but it's cleaner than any of the other solutions I've come across.
#implementation NonDisappearingView
-(void)setBackgroundColor:(UIColor *)backgroundColor {
CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
if (alpha != 0) {
[super setBackgroundColor:backgroundColor];
}
}
#end
Then, I add a NonDisappearingView to my cell and add other subviews to it:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UIView *background = [cell viewWithTag:backgroundTag];
if (background == nil) {
background = [[NonDisappearingView alloc] initWithFrame:backgroundFrame];
background.tag = backgroundTag;
background.backgroundColor = backgroundColor;
[cell addSubview:background];
}
// add other views as subviews of background
...
}
return cell;
}
Alternatively, you could make cell.contentView an instance of NonDisappearingView.
My solution is saving the backgroundColor and restoring it after the super call.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
UIColor *bgColor = self.textLabel.backgroundColor;
[super setSelected:selected animated:animated];
self.textLabel.backgroundColor = bgColor;
}
You also need to do the same thing with -setHighlighted:animated:.
Found a pretty elegant solution instead of messing with the tableView methods. You can create a subclass of UIView that ignores setting its background color to clear color. Code:
class NeverClearView: UIView {
override var backgroundColor: UIColor? {
didSet {
if UIColor.clearColor().isEqual(backgroundColor) {
backgroundColor = oldValue
}
}
}
}
Obj-C version would be similar, the main thing here is the idea
I created a UITableViewCell category/extension that allows you to turn on and off this transparency "feature".
You can find KeepBackgroundCell on GitHub
Install it via CocoaPods by adding the following line to your Podfile:
pod 'KeepBackgroundCell'
Usage:
Swift
let cell = <Initialize Cell>
cell.keepSubviewBackground = true // Turn transparency "feature" off
cell.keepSubviewBackground = false // Leave transparency "feature" on
Objective-C
UITableViewCell* cell = <Initialize Cell>
cell.keepSubviewBackground = YES; // Turn transparency "feature" off
cell.keepSubviewBackground = NO; // Leave transparency "feature" on
Having read through all the existing answers, came up with an elegant solution using Swift by only subclassing UITableViewCell.
extension UIView {
func iterateSubViews(block: ((view: UIView) -> Void)) {
for subview in self.subviews {
block(view: subview)
subview.iterateSubViews(block)
}
}
}
class CustomTableViewCell: UITableViewCell {
var keepSubViewsBackgroundColorOnSelection = false
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
// MARK: Overrides
override func setSelected(selected: Bool, animated: Bool) {
if self.keepSubViewsBackgroundColorOnSelection {
var bgColors = [UIView: UIColor]()
self.contentView.iterateSubViews() { (view) in
guard let bgColor = view.backgroundColor else {
return
}
bgColors[view] = bgColor
}
super.setSelected(selected, animated: animated)
for (view, backgroundColor) in bgColors {
view.backgroundColor = backgroundColor
}
} else {
super.setSelected(selected, animated: animated)
}
}
override func setHighlighted(highlighted: Bool, animated: Bool) {
if self.keepSubViewsBackgroundColorOnSelection {
var bgColors = [UIView: UIColor]()
self.contentView.iterateSubViews() { (view) in
guard let bgColor = view.backgroundColor else {
return
}
bgColors[view] = bgColor
}
super.setHighlighted(highlighted, animated: animated)
for (view, backgroundColor) in bgColors {
view.backgroundColor = backgroundColor
}
} else {
super.setHighlighted(highlighted, animated: animated)
}
}
}
All we need is to override the setSelected method and change the selectedBackgroundView for the tableViewCell in the custom tableViewCell class.
We need to add the backgroundview for the tableViewCell in cellForRowAtIndexPath method.
lCell.selectedBackgroundView = [[UIView alloc] init];
Next I have overridden the setSelected method as mentioned below.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
UIImageView *lBalloonView = [self viewWithTag:102];
[lBalloonView setBackgroundColor:[[UIColor hs_globalTint] colorWithAlphaComponent:0.2]];
UITextView *lMessageTextView = [self viewWithTag:103];
lMessageTextView.backgroundColor = [UIColor clearColor];
UILabel *lTimeLabel = [self viewWithTag:104];
lTimeLabel.backgroundColor = [UIColor clearColor];
}
Also one of the most important point to be noted is to change the tableViewCell selection style. It should not be UITableViewCellSelectionStyleNone.
lTableViewCell.selectionStyle = UITableViewCellSelectionStyleGray;