I have a xib file with a UITableView for which I want to add a custom section header view using the delegate method tableView:viewForHeaderInSection:. Is there any possibility to design it in Interface Builder and then change some of it's subview's properties programmatically?
My UITableView has more section headers so creating one UIView in Interface Builder and returning it doesn't work, because I'd have to duplicate it, but there isn't any good method of doing it. Archiving and unarchiving it doesn't work for UIImages so UIImageViews would show up blank.
Also, I don't want to create them programmatically because they are too complex and the resulting code would be hard to read and maintain.
Edit 1: Here is my tableView:viewForHeaderInSection: method:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
return nil;
}
CGSize headerSize = CGSizeMake(self.view.frame.size.width, 100);
/* wrapper */
UIView *wrapperView = [UIView viewWithSize:headerSize];
wrapperView.backgroundColor = [UIColor colorWithHexString:#"2670ce"];
/* title */
CGPoint titleMargin = CGPointMake(15, 8);
UILabel *titleLabel = [UILabel labelWithText:self.categoriesNames[section] andFrame:CGEasyRectMake(titleMargin, CGSizeMake(headerSize.width - titleMargin.x * 2, 20))];
titleLabel.textColor = [UIColor whiteColor];
titleLabel.font = [UIFont fontWithStyle:FontStyleRegular andSize:14];
[wrapperView addSubview:titleLabel];
/* body wrapper */
CGPoint bodyWrapperMargin = CGPointMake(10, 8);
CGPoint bodyWrapperViewOrigin = CGPointMake(bodyWrapperMargin.x, CGRectGetMaxY(titleLabel.frame) + bodyWrapperMargin.y);
CGSize bodyWrapperViewSize = CGSizeMake(headerSize.width - bodyWrapperMargin.x * 2, headerSize.height - bodyWrapperViewOrigin.y - bodyWrapperMargin.y);
UIView *bodyWrapperView = [UIView viewWithFrame:CGEasyRectMake(bodyWrapperViewOrigin, bodyWrapperViewSize)];
[wrapperView addSubview:bodyWrapperView];
/* image */
NSInteger imageSize = 56;
NSString *imageName = [self getCategoryResourceItem:section + 1][#"image"];
UIImageView *imageView = [UIImageView imageViewWithImage:[UIImage imageNamed:imageName] andFrame:CGEasyRectMake(CGPointZero, CGEqualSizeMake(imageSize))];
imageView.layer.masksToBounds = YES;
imageView.layer.cornerRadius = imageSize / 2;
[bodyWrapperView addSubview:imageView];
/* labels */
NSInteger labelsWidth = 60;
UILabel *firstLabel = [UILabel labelWithText:#"first" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 0, labelsWidth, 16)];
[bodyWrapperView addSubview:firstLabel];
UILabel *secondLabel = [UILabel labelWithText:#"second" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 20, labelsWidth, 16)];
[bodyWrapperView addSubview:secondLabel];
UILabel *thirdLabel = [UILabel labelWithText:#"third" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 40, labelsWidth, 16)];
[bodyWrapperView addSubview:thirdLabel];
[#[ firstLabel, secondLabel, thirdLabel ] forEachView:^(UIView *view) {
UILabel *label = (UILabel *)view;
label.textColor = [UIColor whiteColor];
label.font = [UIFont fontWithStyle:FontStyleLight andSize:11];
}];
/* line */
UIView *lineView = [UIView viewWithFrame:CGRectMake(imageSize + labelsWidth + bodyWrapperMargin.x * 2, bodyWrapperMargin.y, 1, bodyWrapperView.frame.size.height - bodyWrapperMargin.y * 2)];
lineView.backgroundColor = [UIColor whiteColorWithAlpha:0.2];
[bodyWrapperView addSubview:lineView];
/* progress */
CGPoint progressSliderOrigin = CGPointMake(imageSize + labelsWidth + bodyWrapperMargin.x * 3 + 1, bodyWrapperView.frame.size.height / 2 - 15);
CGSize progressSliderSize = CGSizeMake(bodyWrapperViewSize.width - bodyWrapperMargin.x - progressSliderOrigin.x, 30);
UISlider *progressSlider = [UISlider viewWithFrame:CGEasyRectMake(progressSliderOrigin, progressSliderSize)];
progressSlider.value = [self getCategoryProgress];
[bodyWrapperView addSubview:progressSlider];
return wrapperView;
}
and I would want it to look something like this:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
return nil;
}
SectionView *sectionView = ... // get the view that is already designed in the Interface Builder
sectionView.headerText = self.categoriesNames[section];
sectionView.headerImage = [self getCategoryResourceItem:section + 1][#"image"];
sectionView.firstLabelText = #"first";
sectionView.secondLabelText = #"second";
sectionView.thirdLabelText = #"third";
sectionView.progress = [self getCategoryProgress];
return wrapperView;
}
Edit 2: I'm not using a Storyboard, just .xib files. Also, I don't have an UITableViewController, just an UIViewController in which I added an UITableView.
#Storyboard or XIB. Updated for 2020.
Same Storyboard:
return tableView.dequeueReusableCell(withIdentifier: "header")
Separate XIB (Additional step: you must register that Nib first):
tableView.register(UINib(nibName: "XIBSectionHeader", bundle:nil),
forCellReuseIdentifier: "xibheader")
To load from a Storyboard instead of a XIB, see this Stack Overflow answer.
#Using UITableViewCell to create Section Header in IB
Take advantage of the fact that a section header is a regular UIView, and that UITableViewCell is, too, a UIView. In Interface Builder, drag & drop a Table View Cell from the Object Library onto your Table View Prototype Content.
(2020) In modern Xcode, simply increase the "Dynamic Prototypes" number to drop in more cells:
Add an Identifier to the newly added Table View Cell, and customize its appearance to suit your needs. For this example, I used header.
Use dequeueReusableCell:withIdentifier to locate the cell, just like you would any table view cell.
Don't forget it is just a normal cell: but you are going to use it as a header.
For 2020, simply add to ViewDidLoad the four lines of code:
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 70 // any reasonable value is fine
tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 70 // any reasonable value is fine
{See for example this for a discussion.}
Your header cell heights are now completely dynamic. It's fine to change the length of the texts, etc, in the headers.
(TiP: Purely regarding the storyboard: simply select...
...in storyboard, so that the storyboard will work correctly. This has absolutely no effect on the final build. Selecting that checkbox has absolutely no effect whatsoever on the final build. It purely exists to make the storyboard work correctly, if the height is dynamic.)
In older Xcode, or, if for some reason you do not wish to use dynamic heights:
simply supply heightForHeaderInSection, which is hardcoded as 44 for clarity in this example:
//MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
// This is where you would change section header content
return tableView.dequeueReusableCell(withIdentifier: "header")
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return 44
}
###Swift 2 & earlier:
return tableView.dequeueReusableCellWithIdentifier("header") as? UIView
self.tableView.registerNib(UINib(nibName: "XIBSectionHeader", bundle:nil),
forCellReuseIdentifier: "xibheader")
► Find this solution on GitHub and additional details on Swift Recipes.
I finally solved it using this tutorial, which, largely consists of the following (adapted to my example):
Create SectionHeaderView class that subclasses UIView.
Create SectionHeaderView.xib file and set it's File's Owner's CustomClass to the SectionHeaderView class.
Create an UIView property in the .m file like: #property (strong, nonatomic) IBOutlet UIView *viewContent;
Connect the .xib's View to this viewContent outlet.
Add an initializer method that looks like this:
+ (instancetype)header {
SectionHeaderView *sectionHeaderView = [[SectionHeaderView alloc] init];
if (sectionHeaderView) { // important part
sectionHeaderView.viewContent = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:sectionHeaderView options:nil] firstObject];
[sectionHeaderView addSubview:sectionHeaderView.viewContent];
return sectionHeaderView;
}
return nil;
}
Then, I added an UILabel inside the .xib file and connected it to the labelCategoryName outlet and implemented the setCategoryName: method inside the SectionHeaderView class like this:
- (void)setCategoryName:(NSString *)categoryName {
self.labelCategoryName.text = categoryName;
}
I then implemented the tableView:viewForHeaderInSection: method like this:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
SectionHeaderView *sectionHeaderView = [SectionHeaderView header];
[sectionHeaderView setCategoryName:self.categoriesNames[section]];
return sectionHeaderView;
}
And it finally worked. Every section has it's own name, and also UIImageViews show up properly.
Hope it helps others that stumble over the same wrong solutions over and over again, all over the web, like I did.
Solution Is way simple
Create one xib, make UI according to your Documentation
then in viewForHeaderInSection get xib
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"HeaderView" owner:self options:nil];
HeaderView *headerView = [nibArray objectAtIndex:0];
return headerView;
}
As far as I understand your problem, you want to have the same UIView duplicated multiple times for the multiple section headers you want to be able to display.
If this were my problem, here is how I would solve it.
ORIGINAL SOLUTION
1)
In my UIViewController that owns the table view, I'd also create a view that's a template for the header. Assign that to a IBOutlet. This will be the view you can edit via Interface Builder.
2)
In your ViewDidLoad or (maybe better) ViewWillAppear method, you'll want to make as many copies of that header template UIView as you'll need to display for section headers.
Making copies of UIViews in memory isn't trivial, but it isn't hard either. Here is an answer from a related question that shows you how to do it.
Add the copies to a NSMutableArray (where the index of each object will correspond to the sections... the view in index 0 of the array will be what you return for section 0, view 1 in the array for section 1, ec.).
3)
You will not be able to use IBOutlets for the elements of that section header (because your code only associates outlets with one particular view from the XIB file).
So for this, you'll probably want to use view tag properties for each of the UI elements in your header view that you'll want to modify/change for each different section. You can set these tags via Interface Builder and then refer to them programmatically in your code.
In your viewForHeaderInSection method, you'll do something like:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
return nil;
}
SectionView *sectionView = [self.arrayOfDuplicatedHeaderViews objectAtIndex: section];
// my title view has a tag of 10
UILabel *titleToModify = [sectionView viewWithTag: 10];
if(titleToModify)
{
titleToModify.text = [NSString stringWithFormat:#"section %d", section];
}
return sectionView;
}
Makes sense?
DIFFERENT SOLUTION
1)
You'd still need an array of UIViews (or "Section View" subclassed UIViews) but you could create each of those with successive calls to load the view from it's own XIB file.
Something like this:
#implementation SectionView
+ (SectionView*) getSectionView
{
NSArray* array = [[NSBundle mainBundle] loadNibNamed:#"SectionView" owner:nil options:nil];
return [array objectAtIndex:0]; // assume that SectionView is the only object in the xib
}
#end
(more detail found in the answer to this related question)
2)
You might be able to use IBOutlets on this (but I'm not 100% certain), but tag properties once again might work pretty well.
Related
Whenever I create a tableView,there will be a line at the bottom of the last cell,is there a method to remove it?I don't use the Xib or Storyboard,so please show me the code.Thank you very much!
Sorry I didn't describe the situation clearly,here is the Screenshot,I have two sections in this tableview,and there is a line at the bottom of the last cell
The solution is really easy if you just take a custom cell.
Firstly drag and drop a cell in your TableView and then select the TableView to make it's separator as None like-
Then Assign the UITableViewCell's class top the custom one.
In the TableViewCell in storyboard, take your required Objects like Lables, images or whatever you need. Now, make sure you take a UIView of 1 px at the bottom of the cell. Connect the outlets and add the required constrains.
Your Custom Class may look like -
CustomTableViewCell.h file-
#import <UIKit/UIKit.h>
#interface CustomTableViewCell : UITableViewCell
#property(nonatomic, strong) IBOutlet UILabel *customTextLabel;
#property(nonatomic, strong) IBOutlet UIView *separatorView;
#end
Now in the View Controller, just show or hide the separator at the bottom row of your section.
So, your Datasource methods may look like-
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 2;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if(section == 0){
return 2;
}
else{
return 1;
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
CustomTableViewCell *cell = (CustomTableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"sampleCell"];
if(indexPath.section == 0 && indexPath.row == 1){
cell.separatorView.hidden = YES;
}
else if (indexPath.section == 1 && indexPath.row == 0){
cell.separatorView.hidden = YES;
}
else{
cell.separatorView.hidden = NO;
}
cell.customTextLabel.text = #"Test"; //put whatever you want
return cell;
}
You should get a outlook like-
Hope this helps.
For removing last line i.e last separator of tableview, you have to use below code in UIViewDidload
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
OR if you want to remove particular cell separator use inside cellForRowAtIndexPath method
tableView.separatorStyle = UITableViewCellSeparatorStyleDefault;
if (indexPath.section == 1 && indexPath.row == 1)
{
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
To hide the line below the cell use the following:
// set properties tableView
tableView.separatorStyle = .none
You can design your own custom footer for UITableView
But i don't think by default tableView show any footer.
As Your question is unclear and you haven't provide any screenshot or anything else.
Create your custom view with your desired colour contents and then just assign it to your tableView.
self.tableView.tableFooterView = customFooterView;
Or Maybe you want to hide/remove the tableView LineSeparator
There are two most easy approaches to do this.
Either assign a clear colour to separator or assign none to separator.
self.tableView.separatorStyle = UITableViewSeparatorStyleNone;
or
self.tableView.separatorColor = [UIColor clearColor];
I have a customCell and I need to add more than one UILabel as "tag" to each cell,
My code is like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ID = #"topicCell";
MSPTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
NSArray *labelArray = [TopicLabelArr objectAt:index.row];
for (int i = 0; i < [labelArray count]; i++) {
UILabel *tmpLabel = [UILabel alloc]initwithFrame .....];
tmpLabel.text = [labelArray objectAt:i];
[cell.view addSubview:tmpLabel];
}
return cell;
}
I use Xib to create the custom cell.
What I need is make the for-loop only execute one time on each cell.
However, there are many rows in tableView, and the labels will be created repeatly every times when I scroll up and down. How to improve it? Any idea? Thanks.
When you use dequeueReusableCellWithIdentifier, you are not creating a new MSPTopicCell you (as the name of the method says) reuse a cell.
What does that mean ? You obviously need at least as many cell as how much you are displaying at the same time, but once your start scrolling, the cells which disappear of your scrollview are reused.
The labels you add to the subview are added overtime, even on a reused cell which already got some subviews added, which produce your issue.
There are many ways to fix it, here are some examples:
You can remove the subviews added before adding new ones. Add the following line before your for loop using the following code:
view.subviews.forEach({ $0.removeFromSuperview() }
Use a custom tag for your labels so you can see it they already exist or not :
for (int i = 0; i < [labelArray count]; i++) {
UILabel *tmpLabel = (UILabel *)[self viewWithTag:100+i];
if (tmpLabel == nil)
{
tmpLabel = [UILabel alloc]initwithFrame .....];
tmpLabel.tag = 100 + i;
[cell.view addSubview:tmpLabel];
}
tmpLabel.text = [labelArray objectAt:i];
}
The best solution, in my opinion, since you already use an UITableViewCell subclass : simply directly add some UILabel properties on your MSPTopicCell class, so you don't have to create it in cellForRowAtIndexPath. But maybe this case is not adapted for you since the number of labels depend of the labelArray, which dependents of the position of the cell.
You can create a UIView which will include all your UILabel. However when you reuse the cell at the beginning just remove that view from superview.
So masterview will be removed from your cell.
i.e
[[cell.contentView viewWithTag:101] removeFromSuperview]
UIView *yourViewName = [[UIView alloc]init];
// set your view's frame based on cell.
yourViewName.tag = 101;
for (int i = 0; i < [labelArray count]; i++) {
UILabel tmpLabel = (UILabel )[self viewWithTag:100+i];
if (tmpLabel == nil)
{
tmpLabel = [UILabel alloc]initwithFrame .....];
tmpLabel.tag = 100 + i;
[yourViewName addSubview:tmpLabel];
}
tmpLabel.text = [labelArray objectAt:i];
}
[cell.contentView addSubView:yourViewName];
This process will speed up your cell's scrolling performance as well.
Hope this answer helped you.
This question already has answers here:
Eliminate extra separators below UITableView
(34 answers)
Closed 6 years ago.
i am trying to display a simple UITableView with some data. I wish to set the static height of the UITableView so that it doesn't displays empty cells at the end of the table. how do I do that?
code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"%d", [arr count]);
return [arr count];
}
Set a zero height table footer view (perhaps in your viewDidLoad method), like so:
Swift:
tableView.tableFooterView = UIView()
Objective-C:
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
Because the table thinks there is a footer to show, it doesn't display any cells beyond those you explicitly asked for.
Interface builder pro-tip:
If you are using a xib/Storyboard, you can just drag a UIView (with height 0pt) onto the bottom of the UITableView.
Swift 3 syntax:
tableView.tableFooterView = UIView(frame: .zero)
Swift syntax: < 2.0
tableView.tableFooterView = UIView(frame: CGRect.zeroRect)
Swift 2.0 syntax:
tableView.tableFooterView = UIView(frame: CGRect.zero)
In the Storyboard, select the UITableView, and modify the property Style from Plain to Grouped.
Implemented with swift on Xcode 6.1
self.tableView.tableFooterView = UIView(frame: CGRectZero)
self.tableView.tableFooterView?.hidden = true
The second line of code does not cause any effect on presentation, you can use to check if is hidden or not.
Answer taken from this link
Fail to hide empty cells in UITableView Swift
I can not add comment as of now so adding this as an answer.
#Andy's answer is good and the same results can be achieved with the following line of code:
tableView.tableFooterView = [UIView new];
'new' method belongs to NSObject class and invokes alloc and init methods for UIView.
I tried the code:
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
In the viewDidLoad section and xcode6 showed a warning. I have put a "self." in front of it and now it works fine. so the working code I use is:
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
or you can call tableView method to set the footer height in 1 point, and it will add an last line, but you can hide it too, by setting footer background color.
code:
func tableView(tableView: UITableView,heightForFooterInSection section: Int) -> CGFloat {
return 1
}
looks like last line
override func viewWillAppear(animated: Bool) {
self.tableView.tableFooterView = UIView(frame: CGRect.zeroRect)
/// OR
self.tableView.tableFooterView = UIView()
}
Using UITableViewController
The solution accepted will change the height of the TableViewCell. To fix that, perform following steps:
Write code snippet given below in ViewDidLoad method.
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
Add following method in the TableViewClass.m file.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return (cell height set on storyboard);
}
That's it. You can build and run your project.
in the below method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (([array count]*65) > [UIScreen mainScreen].bounds.size.height - 66)
{
Table.frame = CGRectMake(0, 66, self.view.frame.size.width, [array count]*65));
}
else
{
Table.frame = CGRectMake(0, 66, self.view.frame.size.width, [UIScreen mainScreen].bounds.size.height - 66);
}
return [array count];
}
here 65 is the height of the cell and 66 is the height of the navigation bar in UIViewController.
The question is what's the right-most way to display a separator in the last cell in a table/section.
Basically this is what I am after.
This is from the native music app, which makes me think that it should be possible to achieve just by means of UITableView, they would not be using some private API for cell separators, right?
I know you can get away without using actual separators, but adding one pixel line in the bottom of the cell. But I'm not a fan of this approach because
When a cell is selected/highlighted its separator and the separator of the previous cell are automatically hidden (see the second screenshot with "You've got to be crazy" selected). And this is
something I want UITableView to handle instead of doing myself if I use one-pixel line
(which is especially handy when cell separators do not extend all
the way to the edge of the table view, separators and selected cell background do not look nice together).
I would like to keep my cells as flat as possible for scrolling performance.
Also there is something in UITableView reference that makes me think that there is an easy way to get what I want:
In iOS 7 and later, cell separators do not extend all the way to the
edge of the table view. This property sets the default inset for all
cells in the table, much like rowHeight sets the default height for
cells. It is also used for managing the “extra” separators drawn at
the bottom of plain style tables.
Does somebody know how exactly to use these “extra” separators drawn at the bottom of plain style tables? Because this is exactly what I need.
I thought assigning separatorInsetto the UITableView, not UITableViewCell would do the trick, but it does not, the last cell is still missing its separator.
Right now I only see one option: to have a custom section footer to mimic the separator for the last cell. And this is not good, especially if you want to have an actual section footer using tableView:titleForFooterInSection: method.
This worked flawlessly for me to add a separator to the last cell. have fun!
self.tableView.tableFooterView = [[UIView alloc] init];
When you add headerView or footerView to your TableView, last separator line will disappear.
Example below will let you make workaround for showing separator on the last cell. The only thing you have to implement more is to make this separator disappearing after selecting cell, so behavior is the same like in the rest of cells.
For Swift 4.0
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let result = UIView()
// recreate insets from existing ones in the table view
let insets = tableView.separatorInset
let width = tableView.bounds.width - insets.left - insets.right
let sepFrame = CGRect(x: insets.left, y: -0.5, width: width, height: 0.5)
// create layer with separator, setting color
let sep = CALayer()
sep.frame = sepFrame
sep.backgroundColor = tableView.separatorColor?.cgColor
result.layer.addSublayer(sep)
return result
}
I just found something that works for me. In a few words: give your UITableView a tableFooterView and set its frame's height to 0. This makes an actual separator show, with the right insets.
In more details: if you are using the storyboard, drag a UIView to the bottom of your Table View (in the tree view on the left) so it shows just below Table View Cell, at the same hierarchical level. This creates a tableFooterView. Of course this can be done programmatically as well.
Then, in your UITableViewController's implementation:
UIView *footerView = self.tableView.tableFooterView;
CGRect footerFrame = footerView.frame;
footerFrame.size.height = 0;
[footerView setFrame:footerFrame];
Let me know if that works for you! It might also work for the first separator if you use a tableHeaderView instead, I haven't tried it though.
So here's a super simple solution in Swift 4 which works:
Inside override func viewDidLoad(){}, I simply implemented this line of code:
self.tableView.tableFooterView = UIView(frame: .zero)
Hence it ensures that only the last cell gets the separator inset.
This worked perfectly for me, hope it does for you too!
this will do exactly what you want .. even though the line will take the entire width of the cell:
in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
self.tableView.separatorColor = [UIColor redColor];
self.tableView.separatorInset = UIEdgeInsetsZero;
You can do something like this if you are not using sections:
- (void)viewDidLoad
{
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(insetLeftSide, 0, width - insetRightSide, 1)];
}
If you are using sections implement the footer in each section as a one point View in the methods
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
}
This will allow you to have a separator for your last cell which in fact is not a separator is a footer that you can play with it and make it look like a separator
Swift 3
I found this old issue, so I tried the same in Swift:
tableView.tableFooterView = UIView()
tableView.tableFooterView?.height = 0 // Maybe not necessary
But it lead to another problem (in my case). So I solved it differently. I added an extra cell at the end of this section, empty and with height equal to zero:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return previousNumberOfRows + 1
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.row == previousNumberOfRows ? 0 : previousCellHeight
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == items.count {
return UITableViewCell()
} else {
previousCellInitialization()
}
This is a less concise solution, but works in more cases, if you have multiple sections, existing footer view(s)...
I had a similar issue and found a workaround. Apple hides the last uitableviewcell separator and then displays it if you select the cell or if you call select and deselect on that cell.
So I taught the best is in - (void)layoutSubviews. The separator view is called _separatorView and it's a private property. Using the cell's selected / highlighted states you can come up with something like this:
- (void)layoutSubviews
{
// Call super so the OS can do it's layout first
[super layoutSubviews];
// Get the separator view
UIView *separatorView = [self valueForKey:#"_separatorView"];
// Make the custom inset
CGRect newFrame = CGRectMake(0.0, 0.0, self.bounds.size.width, separatorView.frame.size.height);
newFrame = CGRectInset(newFrame, 15.0, 0.0);
[separatorView setFrame:newFrame];
// Show or hide the bar based on cell state
if (!self.selected) {
separatorView.hidden = NO;
}
if (self.isHighlighted) {
separatorView.hidden = YES;
}
}
Something like this.
In iOS 8, if you have a plain style UITableView, and add a footer, the last separator is gone. However, you can easily recreate it by adding a custom separator view to the footer.
This example exactly mimics Today Widget separator style
override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
//create footer view
let result = UIView()
result.frame.size.height = tableView.rowHeight
//recreate last cell separator, which gets hidden by footer
let sepFrame = CGRectMake(15, -0.5, tableView.frame.width, 0.5);
let vibrancyEffectView = UIVisualEffectView.init(effect: UIVibrancyEffect.notificationCenterVibrancyEffect())
vibrancyEffectView.frame = sepFrame
vibrancyEffectView.contentView.backgroundColor = tableView.separatorColor
result.addSubview(vibrancyEffectView)
return result
}
If you add an empty footer then the tableview will remove all the remaining line separators (for non-existent cells) but will still include the separator for the last cell:
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
return [UIView new];
}
I used the table view style "grouped" to add this line:
And furthermore it avoids sticking the last separator line to stick on the screen when scrolling out!
Works in iOS 7.x and 8.x (put it in cellForRowAtIndexPath):
(PS substitute "max elements of your datasource" with your array datasource count)
if (row == <max elements of your datasource>-1) {
UIView* separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, cell.contentView.frame.size.height-1, self.view.frame.size.width, 1)];/// change size as you need.
separatorLineView.tag = 666;
separatorLineView.backgroundColor = [UIColor colorWithRed: 204.0/255.0 green: 204.0/255.0 blue: 204.0/255.0 alpha:1.0];
UIView *separator = [cell.contentView viewWithTag:666];
// case of orientation changed..
if (separator.frame.size.width != cell.contentView.frame.size.width) {
[[cell.contentView viewWithTag:666] removeFromSuperview];
separator = nil;
}
if (separator==nil) [cell.contentView addSubview:separatorLineView];
}
This is definitely help. Working.
but set separator "none" from attribute inspector.
Write following code in cellForRowAtIndexPath method
if(indexPath.row==arrData.count-1){
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0,
cell.contentView.frame.size.height - 1.0,
cell.contentView.frame.size.width, 1)];
lineView.backgroundColor = [UIColor blackColor];
[cell.contentView addSubview:lineView];
}
The code below worked for me:
UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 0.35)];
footerView.backgroundColor = [UIColor whiteColor];
CGRect frame = footerView.frame;
frame.origin.x = 15;
frame.size.width = frame.size.width - 15;
UIView *blackView = [[UIView alloc] initWithFrame:frame];
[blackView setBackgroundColor:[UIColor blackColor]];
blackView.alpha = 0.25;
[footerView addSubview:blackView];
tableView.tableFooterView = footerView;
Hope it works for you too.
I'm new in iOS programming that's why I'm looking for the most efficient solution to my problem.
What I want to achieve is to display in UITableViewCell with a name (some text) and under each name some filled little rectangles with a number inside, similar to badges.
My first idea is to create a UIView that will represent the badge and in a custom UITableViewCell I will add these rectangles as subviews.
The second idea is to create only one UIView that will draw all the little rectangles.
My question is, which is the better performing solution knowing that:
the number of cells will be max. 20 and the total number of rectangles no more than 50
The number of rectangles displayed in a cell is different
I want to reuse the cells, so I have to update/redraw the cell content for each row
I want to avoid the cell selection view problem that "hides" the subviews
Of course any other solution is appreciated.
Thanks in advance,
hxx
What i would suggest is to sub class the UITableViewCell and make the customization u need in it.The customized view can have a label and rectangles below it.
The rectangles can be small custom buttons with background images (if you have any or give it a background color) and title as your number.You would have to ,however calculate their width based on the width of your table to accomodate the maximum number of rectangles.
You can disable the selection of the table in the xib or you can do it programmatically like so cell.selectionStyle = UITableViewCellSelectionStyleNone; and do not implement didSelectRowAtIndexPath
I have followed the approach of subclassing the cell for my tables to customize their look and feel and it works good.I hope this helps.
A Good tutorial to begin with subclassing can be found here
http://howtomakeiphoneapps.com/how-to-design-a-custom-uitableviewcell-from-scratch/1292/
Why you are not creating cell in -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath Here you can defines your custom type cell which will also reuse and whenever you want you can add the different thing to cell like this.
static NSString *CellIdentifier = #"Cell";
UILabel *RequestSentTo;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
cell.selectionStyle=UITableViewCellSelectionStyleNone;
RequestSentTo = [[UILabel alloc] initWithFrame:CGRectMake(11, 2, 286, 40)];
RequestSentTo.backgroundColor = [UIColor clearColor];
RequestSentTo.tag = 200;
RequestSentTo.numberOfLines = 3;
RequestSentTo.font=[UIFont boldSystemFontOfSize:15.0];
RequestSentTo.textColor = [UIColor blackColor];
RequestSentTo.lineBreakMode=UILineBreakModeWordWrap;
[cell.contentView addSubview:RequestSentTo];
} else {
RequestSentTo=(UILabel*)[cell.contentView viewWithTag:200];
}
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:#"Shift Request for "];
[string appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%# by ",dateStr] attributes:nil]];
[string appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"Dr. %#",notificationsObj.doctorName] attributes:purpleTextAttributes]];//purpl
RequestSentTo.attributedText=string;
RequestSentTo.lineBreakMode=UILineBreakModeWordWrap;
RequestSentTo.numberOfLines = 3;
Whenever you want you can add the things you want with reusing cell. Hope this helps
2 methods come into my mind.
You can put the components as subview inside UITableViewCell(Through XIB or programatically subclassing UITableViewCell) and use it in UITableView.
You can subclass UITableViewCell, and override the -(void)drawRect method and draw all the components that you wish to be displayed on cell.
See if can help.
You can create a new class extends to UITableViewCell, which means to rewrite UITableViewCell as your own cell named as MyTestCell.
And in this Cell you call create your properties, like labels and views, and add those to your new cell.
like add this to MyTestCell.h
#property (nonatomic, retain) UILable *myLable1;
#property (nonatomic, retain) UIView *mySubview1;
MyTestCell.m
_myLable1 = .....
_mySubview = .....
[self addSubview: _myLbale1];
[self addSubview: _mySubview1];
And when use, u can work like this
static NSString *CellIdentifier = #"MyCell";
MyTableViewCell *cell = [tableview dequeReuseID:CellIdentifier];
if (cell == nil) {
cell = [MyTableViewCell alloc] init.........
}
//And you can sign your property here in your cell
cell.myLable1 = ....
cell.myView1 = .....
return cell;
}
If your strings add to the lable is different,make the lable.height is different. you can use code like this
CGSize labelSize = [str sizeWithFont:[UIFont boldSystemFontOfSize:17.0f]
constrainedToSize:CGSizeMake(280, 100)
lineBreakMode:UILineBreakModeCharacterWrap]; //check your lableSize
UILabel *patternLabel = [[UILabel alloc] initWithFrame:CGRectMake(35, 157, labelSize.width, labelSize.height)];
patternLabel.text = str;
patternLabel.backgroundColor = [UIColor clearColor];
patternLabel.font = [UIFont boldSystemFontOfSize:17.0f];
patternLabel.numberOfLines = 0;// must have
patternLabel.lineBreakMode = UILineBreakModeCharacterWrap;// must have
add this to your cell, and make it dynamically resize your lable as well as your cell! And also you have to dynamically set high for your tableView Row height.(Do know what is dynamically resize?)
See this:
rewrite the method setMyLable1 in MyTableViewCell.m
-(void)setMyLable1:(UILable*)aLable
{
//in here when never your sign your alabel to your cell (like this : cell.myLable1) this method will be call and u can get the size of your string and set this label's height
//get string size StringSzie
[_myLable1 setFrame:CGRectMake(10,10,stringSize.width,stringSize.height)];
//And resize your cell as well
[self setFrame:CGRectMake(0,0,_myLable1.frame.size.width+20,_myLable1.frame.size.height+20)];
//done!!!
}
OK you get a automactically reszie cell for yourself and you have to dynamically reset height for your row in tableView too!!!!!
What do you need is called custom cell
Here is good tutorial for it
customize table view cells for uitableview