Adding UIButton to UITableView section header - ios

I have a UITableView with 1 section and for the section header, I would like to keep everything about the header the same but simply add a button on the right side. I cannot put the button in the navigation controller because the two available spots to put such a button are already occupied by other buttons.
Here you can see the result of what I tried to do. Now all I want to do is put an add button on the right side of the header, but what I have tried didn't work. I also tried some other solutions on StackOverflow, but they did not accomplish quite what I wanted to do. Additionally, if there is a better way of creating an add button, please let me know. I tried using a UIBarButtonItem but I couldn't figure out how to add its view to an actual view. Thanks!
This is what I have so far in my delegate:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIButton *addButton = [[UIButton alloc] init]; //I also tried setting a frame for the
button, but that did not seem to work, so I figured I would just leave it blank for posting
the question.
addButton.titleLabel.text = #"+";
addButton.backgroundColor = [UIColor redColor];
[self.tableView.tableHeaderView insertSubview:addButton atIndex:0];
//I feel like this is a bad idea
return self.tableView.tableHeaderView;
}
- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 50;
}

The problem is that your self.tableView.tableHeaderView is nil at this point in time, therefore you can't use it. So what you need to do is, create a UIView, add title, and button to it, style them, and return it.
This should add a title and button to your section header, you still need to style the title with correct font and size but will give you an idea.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
CGRect frame = tableView.frame;
UIButton *addButton = [[UIButton alloc] initWithFrame:CGRectMake(frame.size.width-60, 10, 50, 30)];
[addButton setTitle:#"+" forState:UIControlStateNormal];
addButton.backgroundColor = [UIColor redColor];
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 30)];
title.text = #"Reminders";
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[headerView addSubview:title];
[headerView addSubview:addButton];
return headerView;
}

From iOS 6, you can also implement
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
in your UITableViewDelegate. For example:
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section {
if (section == 0) {
if ([view.subviews.lastObject isKindOfClass:[UIButton class]]) {
return;
}
UIButton *button = [UIButtonbuttonWithType:UIButtonTypeSystem];
button.frame = CGRectMake(view.frame.size.width - 160.0, 0, 160.0, view.frame.size.height); // x,y,width,height
[button setTitle:#"My Button" forState:UIControlStateNormal];
[button addTarget:self action:#selector(sectionHeaderButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:button];
}
}

If you are interested in Swift solution:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let frame = tableView.frame
let button = UIButton(frame: CGRectMake(5, 10, 15, 15)) // create button
button.tag = section
// the button is image - set image
button.setImage(UIImage(named: "remove_button"), forState: UIControlState.Normal) // assumes there is an image named "remove_button"
button.addTarget(self, action: #selector(TestController.remove(_:)), forControlEvents: .TouchUpInside) // add selector called by clicking on the button
let headerView = UIView(frame: CGRectMake(0, 0, frame.size.width, frame.size.height)) // create custom view
headerView.addSubview(button) // add the button to the view
return headerView
}
You might want to specify the height of the header in the section. This must be in sync with the height of the custom view:
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat(30)
}

You have 2 choices, both via your tableView.delegate. The first involves implementing the delegate method
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
which enables you to basically return anything you want in a custom view, which will be used as the header for your desired section. This could be as simple as
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIButton *addButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
[addButton addTarget:self action:#selector(SomeMethod:) forControlEvents:UIControlEventTouchUpInside];
return addButton;
}
which will put a round info button (centered) as your header. But more likely you'll want to create an actual UIView object and populate it with your button (and a section title label?) and lay everything out as you like [see others' answers for how to do this].
However, the second approach is probably better for the general case of simply adding a button to (one of) your section headers [I know you stated 1 section, but a lot of folks will probably land on this question wanting to do similar in a multi-section table]. This involves implementing the delegate method
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
and basically adding your button to the header after-the-fact; specifically
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
// Remove old button from re-used header
if ([view.subviews.lastObject isKindOfClass:UIButton.class])
[view.subviews.lastObject removeFromSuperview];
if (section == MySectionNumberWithButton) {
UIButton *addButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
[addButton addTarget:self action:#selector(SomeMethod:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:addButton];
// Place button on far right margin of header
addButton.translatesAutoresizingMaskIntoConstraints = NO; // use autolayout constraints instead
[addButton.trailingAnchor constraintEqualToAnchor:view.layoutMarginsGuide.trailingAnchor].active = YES;
[addButton.bottomAnchor constraintEqualToAnchor:view.layoutMarginsGuide.bottomAnchor].active = YES;
}
}
Note, because the tableView re-uses headers, in a multi-section table you must make sure to remove any button you might have previously added, otherwise you'll eventually end up with buttons in unwanted sections. Then, if this is the right section, add the button to the existing header. Note, I'm using NSLayoutAnchor (iOS 9+) to layout the button for brevity; you can do the same with NSLayoutConstraint (iOS 6+)
This approach has the distinct advantage that - for just adding a button - you dont have to recreate the regular layout to match all your other (non-button) headers, or worry about margins changing when you rotate your device, etc. In particular, the header title is untouched and will retain any global appearance settings you may have defined for table headers (eg font, color, ...), all of which you'd have the messy job of re-creating if you took the tableView:viewForHeaderInSection: approach.

You can do that by using the below code, you can put any type of view in header view but you have to specify the frame for it.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view=[[UIView alloc]init];
UIButton *addButton=[UIButton buttonWithType:UIButtonTypeContactAdd];
addButton.frame=CGRectMake(250, 0, 100, 50);
[view addSubview:addButton];
[tblView.tableHeaderView insertSubview:view atIndex:0];
//I feel like this is a bad idea
return view;
}

Swift 4 :
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let frame = tableView.frame
let button = UIButton(frame: CGRect(x: 5, y: 10, width: 15, height: 15))
button.tag = section
button.setImage(UIImage(named: "remove_button"), for: UIControl.State.normal)
button.addTarget(self,action:#selector(buttonClicked),for:.touchUpInside)
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
headerView.addSubview(button)
return headerView
}
This functions handles the button click:
#objc func buttonClicked(sender:UIButton)
{
if(sender.tag == 1){
//Do something for tag 1
}
print("buttonClicked")
}

If you want to use Interface Builder to build your section header, you can use a UITableViewCell as the header. Create a prototype cell for your header in your UITableView, customize as you would any other cell, including adding labels, buttons, and constraints.
You can instantiate in your UITableViewController lazily:
lazy var headerCell: MyHeaderTableViewCell = {
let cell = tableView.dequeueReusableCell(withIdentifier: "Header") as! MyHeaderTableViewCell
return cell
}()
To make it the header:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return headerCell
}

Related

UITableViewController - Drop down table view to show a new UIView

I'm trying to design a UITableViewController such that when the user hits the "Search" button on the navigation bar, the table view drops down and a UIView drops down from the navigation bar. Then, when the user taps anywhere outside of the UIView, the UIView should retract and the table view should return to its original position.
Currently, I have the following method that is the selector for the "Search" button. Note - self.searchBar is a custom UIView subclass.
Is there a cleaner/better way to accomplish this? Also I'm not sure how to get rid of the view after the user taps out of the search menu... I'm guessing I should call, [self.searchBar removeFromSuperview]; but not sure in which delegate method to put that line.
Thanks!
- (void)_showSearchMenu
{
CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height * .25);
frame.origin.y = CGRectGetMaxY(self.navigationController.navigationBar.frame) - frame.size.height;
self.searchBar.frame = frame;
[self.navigationController.navigationBar.superview insertSubview:self.searchBar belowSubview:self.navigationController.navigationBar];
[UIView animateWithDuration:0.5 animations:^{
CGRect frame = self.searchBar.frame;
frame.origin.y = CGRectGetMaxY(self.navigationController.navigationBar.frame);
self.searchBar.frame = frame;
self.tableView.contentOffset = CGPointMake(0, -250);
}];
}
To be more clear, I'm trying to achieve something similar to the effect seen in the HotelTonight app here (the second screen shows what happens when you hit the top right bar button)
This is I think the best approach for that, use these delegates:
(CGFloat) tableView:(UITableView *)tableView
heightForHeaderInSection:(NSInteger)section (UIView *)
tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section
How:
Create a BOOL isOpen with a default value of NO
When you click the Search Button, implement this:
(void) searchButtonTouch:(id)sender {
isOpen = (isOpen) ? YES : NO;
isOpen = !isOpen;
[self.urTableView reloadData];
}
Now in your delegates:
(CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return (isOpen) ? 170.0f : 0.0f;
}
(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
CGFloat height = [self tableView:tableView heightForHeaderInSection:section];
UIView *vw = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, height)];
vw.backgroundColor = [UIColor yellowColor];
// add other controls here in your UIView
// or
// just add a UIView at top of your UITableView
// then add other controls to it (if ur using storyboard)
return vw;
}
Add Tapgesture on superview
In TapGesture Action check in if is searchBar view visible
If Visible hide DropDown view by setting new frame with height zero
You can Add Tap Gesture Programmatically or from Interface Builder , You can use its delegate method "shouldReceiveTouch" or any other custom action.
Gesture Implementation

refresh the action of a IBAction

I have a UIButton that scrolls my UICollectionView to the right. The button is added to the superview of my UICollectionView soo it floats on top of the cells.
But I can only let the UIAction take place once. How do I replicate the action or refresh the button so that I can use it again.
Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(910, 345 , 100, 100);
button.backgroundColor = [UIColor purpleColor];
[self.collectionView.superview addSubview:button];
[button addTarget:self action:#selector(changeContentOffset:) forControlEvents:UIControlEventTouchUpInside];
}
- (IBAction)changeContentOffset:(id)sender {
[self.collectionView setContentOffset:CGPointMake(910, -65) animated:YES];
}
You can add the button to self.view
or
In your - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
method you can simply add the button to cell content view [cell.contentView addSubview: button];
If you set a breakpoint in the line inside the IBAction, did you stop only once ? According to your question, you want to scroll right each time you click on that button, but your method doesn't do so.
The method set the contentOffset of your UICollectionViewthe first time you tap on the button, but for the other times, since the UICollectionViewis already shifted, you cannot see any change. Try replacing your method with :
- (IBAction)changeContentOffset:(id)sender {
CGPoint offset = self.collectionView;
offset.x += 910;
offset.y -= 65;
[self.collectionView setContentOffset:offset animated:YES];
}
You have to change this method to depend of the collectionView actual position...
self.collectionView.paginateEnabled = YES;
- (IBAction)changeContentOffset:(id)sender {
float x = cellWidth + self.collectionView.contentOffset.x;
[self.collectionView setContentOffset:CGPointMake(910, x) animated:YES];
}

Hiding UITableView footer

I am unable to hide my UITableView footer (i.e. setting it's height to 0 and animating the transition).
I tried to wrap tableView.tableViewFooter.height = 0 (and tableView.tableViewFooter = nil) between [tableView beginUpdates] and [tableView endUpdates] but it doesn't work.
Implementing the method below creates another footer.
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 50;
}
Is there a difference between a tableview footer and a section footer? I created mine by dropping a button under my tableview in my storyboard.
Any ideas to do this simply?
Ok here's how I solved this problem.
- (void)refreshTableFooterButton
{
if (should be visible) {
self.tableView.tableFooterView = self.tableFooterButton;
} else {
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
}
}
I used [[UIView alloc] initWithFrame:CGRectZero instead of nil to prevent unwanted empty cells to appear in the tableview.
I didn't need an animation block, but other people might.
[UIView animateWithDuration:0.5 animations:^{
}];
Also, i had to keep a strong reference to my tableFooterButton IBOutlet. That was part of why my initial attempt at solving the problem failed. I'm not sure if having a strong reference to an IBOutlet is good practice though. Feel free to leave a comment about that.
using the following code :
self.tableView.tableFooterView?.hidden = false
This should do the trick. There are other alternatives such as this answer here.
tableView.sectionHeaderHeight = 0.0;
tableView.sectionFooterHeight = 0.0;
-(CGFloat)tableView:(UITableView*)tableView heightForHeaderInSection:(NSInteger)section
{
return 1.0;
-(CGFloat)tableView:(UITableView*)tableView heightForFooterInSection:(NSInteger)section
{
return 1.0;
}
-(UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section
{
return [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
}
-(UIView*)tableView:(UITableView*)tableView viewForFooterInSection:(NSInteger)section
{
return [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
}`
use following methods :
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 0.0f;
}
-(UIView*)tableView:(UITableView*)tableView viewForFooterInSection:(NSInteger)section
{
return [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
}`
So in the end here's the simplest solution I can give you. So as you can see I just put the .m (for simplicity).
There is a difference between table footer and section footer. What you're looking for is the section footer.
I also added an animation block for adding and removing it.
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
UIButton *_footerButton;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self createAndAddFooterButton];
}
-(void) buttonPressed:(id)button
{
[UIView animateWithDuration:2 animations:^{
self.tableView.tableFooterView = nil;
}];
}
- (void)createAndAddFooterButton
{
// here you create the button
_footerButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[_footerButton setTitle:#"Button Title" forState:UIControlStateNormal];
[_footerButton sizeToFit];
[_footerButton addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[UIView animateWithDuration:2 animations:^{
self.tableView.tableFooterView = _footerButton;
}];
}
#end
My problem was that the clear line was the footer which I could only define as 1 pixel high and not 0 (as I do not want to have a footer).
The only way I could fix it was to provide a view in viewForFooterInSection and set the background colour to the same as the header, so that visually you don't notice, and when I animate-in new rows they do not show through the top of the header where the clear line (footer) was.
override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let blankView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 1))
blankView.backgroundColor = {Same colour as Header}
return blankView

Hide remove separator line if UITableViewCells are empty [duplicate]

This question already has answers here:
Eliminate extra separators below UITableView
(34 answers)
Closed 6 years ago.
I have a UITableView and I have only 3 rows in it, and I can see those 3 rows. The problem is the cells that are empty: I can see lines there. I don't want to see those lines.
Any idea how to remove those lines?
Below is image for what I am looking for.
Even simpler than Andrey Z's reply:
Simply make a bogus tableFooterView in your UITableView class:
self.tableFooterView = [UIView new]; // to hide empty cells
and Swift:
tableFooterView = UIView()
You can hide UITableView's standard separator line by using any one of the below snippets of code.
The easiest way to add a custom separator is to add a simple UIView of 1px height:
UIView* separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 1)];
separatorLineView.backgroundColor = [UIColor clearColor]; // set color as you want.
[cell.contentView addSubview:separatorLineView];
OR
self.tblView=[[UITableView alloc] initWithFrame:CGRectMake(0,0,320,370) style:UITableViewStylePlain];
self.tblView.delegate=self;
self.tblView.dataSource=self;
[self.view addSubview:self.tblView];
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 10)];
v.backgroundColor = [UIColor clearColor];
[self.tblView setTableHeaderView:v];
[self.tblView setTableFooterView:v];
[v release];
OR
- (float)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
// This will create a "invisible" footer
return 0.01f;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
// To "clear" the footer view
return [[UIView new] autorelease];
}
OR
And also check nickfalk's answer, it is very short and helpful too.
And you should also try this single line,
self.tableView.tableFooterView = [[UIView alloc] init];
Not sure but it's working in all the version of iOS that I checked, with iOS 5 and later, up to iOS 7.
Updated answer for swift & iOS 9. This works for tableviews of the .Plain variety.
tableView.tableFooterView = UIView()
Transparent UIView as a tableView footer with 1px height will do the trick.
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 1)];
v.backgroundColor = [UIColor clearColor];
[self.tableView setTableFooterView:v];
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
Use this Code for remove separator line for empty cells.
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
// This will create a "invisible" footer
return 0.01f;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
return [UIView new];
// If you are not using ARC:
// return [[UIView new] autorelease];
}
Just returning an empty UIView() in viewForFooterInSection worked for me:
override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView()
}
Please try the following code:
self.tblView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
if ([self.tblView respondsToSelector:#selector(setSeparatorInset:)])
{
[self.tblView setSeparatorInset:UIEdgeInsetsZero];
}
// write in view did load
tableForBrands.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
None of suggested answers were suitable for my similar problem. Implementing this method in tableView delegate finally worked:
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
If you are using Storyboards you can just drag and drop an UIView into your UITableView below your cells and set its height to 0. (Have only tested in an iOS 8 project)
I guess this is what you are looking for.
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
return 1.0f;
}
You can set the table view to be grouped instead of plain - this changes the look a bit but at least removes the extra lines.
I had this exact problem and ended up changing to grouped views. Looks a lot better.
Some of previous suggestions contain a BIG conceptual error:
if You do:
[cell addSubview: ....
even time a cell is "reused", you will add a new subview for the divider!
avoid it in two ways:
a) use a TAG, and:
1) ask for a subview for that tag
let divider = cell.viewWithTag(TAG) ...
2) if present, do NOT add another subview
3) if NOT present add AND tag it.
b) create a custom view and ADD your custom divider in "init" "awakeFromNib" of custom cell.
code for a):
if let divider = cell.viewWithTag(DIVIDER_TAG) as? UIView{
// nothing.. eventually change color bases in IndexPath...
}else{
let frame = CGRectMake(0, cell.frame.height-1, cell.frame.width, 1)
divider.tag = DIVIDER_TAG
divider.backgroundColor = UIColor.redColor()
cell.addSubview(divider)
}
Swift Solution:
tableView.tableFooterView = UIView(frame: CGRectZero)
Worked on Xcode 7.2
Inside the cellForRowAtIndexPath
let separatorLineView:UIView = UIView(frame: CGRectMake(0,0,self.tableview.bounds.width,0.5))
separatorLineView.backgroundColor = tableView.separatorColor
cell!.contentView.addSubview(separatorLineView)

iOS can't select segment in UISegmentedControl

I have a UISegmentedControl in a headerview in a UITableView. For some reason, I cannot select any segments, whenever I do, it automatically selects the last segment in the array and I cannot select anything else. When I remove the action, I can select which ever. No idea what's going on.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section == 1) {
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0,0, 320, 40)]; // x,y,width,height
NSArray *itemArray = [NSArray arrayWithObjects:#"E01", #"E02", #"E03", #"E05", #"E06", #"T05", #"E17", #"E19", nil];
control = [[UISegmentedControl alloc] initWithItems:itemArray];
[control setFrame:CGRectMake(30.0, 0, (tableView.bounds.size.width) - 60, 40)];
[control setSelected:YES];
[control setSegmentedControlStyle:UISegmentedControlStylePlain];
[control addTarget:self action:#selector(unitSegmentControl:) forControlEvents:UIControlEventValueChanged];
[headerView addSubview:control];
return headerView;
}
return nil;
}
I'm not aware what calling setSelected does on a UISegmentedControl, but I would suggest you use:
control.selectedSegmentIndex = anNSInteger;
to set the initial selected segment. I doubt that's your issue though.
Something else to consider if you have a scrolling table view,
viewForHeaderInSection
will get called for that section every time that header scrolls onto the screen, meaning that your segmented control will get reinitialized every time the header gets scrolled onto the screen, and you will lose your selected index.
Other than that maybe you could post the code for your unitSegmentControl selector. Possibly it's causing the table view to reload and your viewForHeaderInSection to be called again, thus reinitializing your segmented controller.

Resources