Customizing Header and Footer of Table View with Swift - ios

I need to make a custom header for my tableview. for this I created a class of UITableViewHeaderFooterView type, but I can not select the header of the tableview on the storyboard to set up the class. if it were a static table header appear, but it is not visible to dynamic tables. How can I make this setting?
Note: I use xcode 7.3.1
I'm trying to do something like this, but the storyboard:
https://github.com/jeantimex/ios-swift-collapsible-table-section
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterViewWithIdentifier("header") as! NovaListaTableViewHeader
header.titleLabel.text = sections[section].name
header.arrowLabel.text = ">"
header.setCollapsed(sections[section].collapsed)
header.section = section
header.delegate = self
return header
}

There are several ways to do that. Most of do that by programatically and some achieve it via storyboard and program.
so its totally depends on you how you want to achieve it.
I can share a easiest way with you to customise your header and footer section.
if you have good control on storyboard try to create one
UITableViewCell
Now decorate it as you want and in identifier Name it SectionHeader now use it as reuse cell
After that use this Delegate Method i am sharing an objective C delegate,
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
var headerView: SectionHeaderTableViewCell? = tableView.dequeueReusableCellWithIdentifier("SectionHeader")
if (headerView == nil) {
headerView = SectionHeaderTableViewCell(style:UITableViewCellStyle.Subtitle, reuseIdentifier:"SectionHeader")
}
headerView!.textLabel!.text = "Hello World"
return headerView;
}
now do same thing for footer.

Related

Reload only one section header in UITableView

I'm using a custom cell as a section header in my UITableView. In that cell there are three buttons. If any of the buttons are clicked in that section's cell, it should reload that custom section cell only, not any rows. Is this possible?
I was using the following code to reload that cell:
tableViewHome.reloadSections([1], with: UITableViewRowAnimation.none)
It's hiding my section cell and distorting my entire table.
UPDATE
I'm using UITableView and following code I'm using:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableViewHome.dequeueReusableCell(withIdentifier: "header") as! HeaderTableViewCell
cellHeader.filter1btn.addTarget(self, action: #selector(filterBtnAction(_:)), for: .touchUpInside)
cellHeader.filter2Btn.addTarget(self, action: #selector(filterBtnAction(_:)), for: .touchUpInside)
cellHeader.filter3btn.addTarget(self, action: #selector(filterBtnAction(_:)), for: .touchUpInside)
return cellHeader
}
#IBAction func filterBtnAction(_ sender: UIButton) {
print(sender.tag)
tableViewHome.reloadSections([1], with: UITableViewRowAnimation.none)
}
I'm a little unclear as to what's going on here, but it sounds like there is a UITableView concepts worth explaining here:
UITableView has its own concept of a cell, implemented as UITableViewCell, and its own concept of a header/footer, implemented as UITableViewHeaderFooterView.
Depending on which of these two you meant, there are a few things you can do to get the intended effect:
The UITableViewCell Approach:
If you're using a UITableViewCell as the first row of a section to act like a "header," and you just want to reload that row to the exclusion of the rest of the section, you can call yourTableViewInstance.reloadRows(at:with:) (Apple Documentation) This method takes an array of IndexPaths, and an animation style. You can pass in the indexPath of the one you want to reload.
The UITableViewHeaderFooterView Approach:
If you're using a proper UITableViewHeaderFooterView then you need to make sure that you're providing the appropriate view when reloading the section. Zack Shapiro outlines the steps you need to take in this answer:
Create a class that's a subclass of UITableViewHeaderFooterView.
Register it with your UITableView instance.
Then in viewForHeaderInSection, you do let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView") as! YourHeaderViewSubclass
The last thing he points out is this:
The deceptive thing is the function calls for a return of UIView? when it really needs a dequeuedReusableHeaderFooterView or reloadData will cause it to disappear.
It depends on which of these two implementation paths you're taking, but this should be enough information to point you in the right direction.
Edit:
Based on the code you added, it looks like you're calling yourTableViewInstance.dequeueReusableCell(withIdentifier:for:) instead of yourTableViewInstance.dequeueReusableHeaderFooterView(withIdentifier:) inside of viewForHeaderInSection.
You need to have a subclass of UITableViewHeaderFooterView and then call it correctly. Create that new subclass, then change this:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableViewHome.dequeueReusableCell(withIdentifier: "header") as! HeaderTableViewCell
// ...
to this:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableViewHome.dequeueReusableHeaderFooterView(withIdentifier: "header") as! HeaderTableView
// ...
You need to follow two steps here:
Create a new class, subclassing UITableViewHeaderFooterView instead of UITableViewCell.
Then use the appropriate class as outlined above.
Yes, It is.
Let's say that this is implementation of your method:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let customCell = .... as! YourCustomCell
customCell.someLabel.text = "Some Data"
//... filling your curstom cell
return customCell
}
You can change it in this way
func updateHeaderView(headerView:YourCustomCell, section: Int) {
customCell.someLabel.text = "Some Data"
//... filling your curstom cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let customCell = .... as! YourCustomCell
self.updateHeaderView(customCell, section)
return customCell
}
And call again self.updateHeaderView(customCell, section) whenever you want, e.g.
func buttonClicked() {
let customCell = self.tableView.headerView(forSection: 0) as! YourCustomCell
self.updateHeaderView(customCell, 0)
}
I think your header view class is extending UITableViewHeaderFooterView class. Add a function in the extension
extension UITableViewHeaderFooterView{
func reloadHeaderCell(){
preconditionFailure("This method must be overridden")
}
}
Now override this in your Header class as below
class HeaderView: UITableViewHeaderFooterView {
override func reloadHeaderCell() {
////// add your logic to reload view
}
}
Now you can simply call below line to refresh views
self.tableView?.headerView(forSection:0)?.reloadHeaderCell()
What I did and working very correctly, Please follow the given answer:
SWIFT 3.1
Step 1:
Firstly I took a view xib, designed that according to my requirement and did register in my required class.
Secondly, did sub class class HeaderView: UITableViewHeaderFooterView of UITableViewHeaderFooterView
Like following image:
In my required class(here homeclass) I did register my xib file for my tableview.
override func viewDidLoad() {
super.viewDidLoad()
tableViewHome.register(UINib(nibName: "HeaderView", bundle: nil), forHeaderFooterViewReuseIdentifier: "HeaderView")
}
Step 2:
Then in my required class i did following:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableViewHome.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView") as! HeaderView
cellHeader.filterAction(cellHeader.filter1Btn)
return cellHeader
}
And it started working as per my requirement, later i added custom delegate for more actions in my class, but by subviewing, its now working.

Detect Change in UISegmentedControl inside UITableView HeaderView

I am trying to control my UITableView based on the selectedSegmentIndex of a UISegmentedControl inside my UITableView header. Essentially, I want this UISegmentedControl to work like Twitter's 'Me' tab. I have the UISegmentedControl inside a UITableView header and it is dequeued using this method:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "sectionDetailsHeaderView") as! SectionDetailsHeaderTableViewCell
return cell
}
I have an outlet for the UISegmentedControl hooked up to SectionDetailsHeaderTableViewCell, but I can't figure out how to detect a change in the UISegmentedControl. I want to set a variable, var segmentedControlValue = Int() to the selectedSegmentIndex every time the value changes and call a function, func chooseDataToDisplay() {} when the value changes. How do I go about doing this?
With the help of #Schomes answer and this post, I was able to figure it out!
1) Add the UISegmentedControl into its own UITableViewCell. I would recommend adding a UIView with a white background behind the UISegmentedControl that covers the entire UITableViewCell so the TableView cells flow behind the UISegmentedControl.
2) Add your custom cell class and hook it up to the UITableViewCell
3) Add an outlet, such as yourSegmentedControl to your custom UITableViewCell class. DO NOT add an action into the custom UITableViewCell class. This is done programmatically in step 4.
4) In the UIViewController or UITableViewController class, add the code below.
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "yourCellIdentifier") as! yourCustomCellClass
header.yourSegmentedControl.selectedSegmentIndex = self.segmentedControlValue
header.yourSegmentedControl.addTarget(self, action: #selector(self.getSegmentValue(sender:)), for: .valueChanged)
return header
}
self.segmentedControlValue should be declared as var segmentedControlValue = Int() at the top of your ViewController class. self.getSegmentValue(sender:) should be declared as:
func getSegmentValue(sender: UISegmentedControl) {
self.segmentedControlValue = sender.selectedSegmentIndex
}
5) You also need to add:
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
This is the height of the header. In my case, the size is 45
6) You can now access self.segmentedControlValue anywhere in your ViewController. It will update when the user taps on a different segment.
segmentedControl.addTarget(self, action: "chooseDataToDisplay:", forControlEvents: .ValueChanged)
Where segmentedControl is your UISegmentedControl. This will call func chooseDataToDisplay(segment: UISegmentedControl) {} every time the value changes.
References:
https://developer.apple.com/reference/uikit/uisegmentedcontrol
The section Behavior of Segmented Controls
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UISegmentedControl.html#//apple_ref/doc/uid/TP40012857-UISegmentedControl

How to Add UIView between two row of UITableview

I know the tableview header and footer but I want to add UIView between the row of tableview in objective-c. and uiview have three button also.
but how can it possible. Anybody can help me..
You can not add UIView in between two row or cell of UITableView. You need to customise your cells. And while filling data to your datasource of UITableView you need some kind of indications which you need to match in your cellForRowAtIndexPath method of UITableView and based on condition you need to show your data cell or custom UIView which is already in your custom cell.
Every UITableViewCell is a child of UIView class.
So in effect all your cells are UIViews which you can customize in any way you want.
So what you want to do is use custom cell. Create a model class for it, customize it as you want.
Suppose I have a UIView and I am creating its object in Controller class in which I have TableView:
var obj_ProfileView : UserProfileView!
In ViewDidLoad Method:
self.obj_ProfileView = Bundle.main.loadNibNamed("UserProfileView", owner: self, options: nil)![0] as! UserProfileView
Then In TableViewFooter Method:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
{
if section == 0
{
//Do as per your requirements
return 184.0
}
else
{
//Do as per your requirements
return 45.0
}
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView?
{
if section == 0
{
return obj_ProfileView
}
else{
return nil
}
}

Renaming header of section of uitableview

How can i rename the title of section header on uitableviewcontroller outside of titleForHeaderInSection ?
So as per the question statement update title without reloading or delegates just using the label reference for updating the title for section.
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView() //set the frame accordingly
let label = UILabel() //set the frame accordingly
//make it global and use it reference for updating the title of the section
view.addSubview(label)
return view
}
I am not using Xcode so please validate just writing code roughly
// If only section 2 has a header
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return section == 2 ? updateHeader(section) : nil
}
func updateHeader(section: Int) -> CustomHeader {
let header = (tableView.footerViewForSection(section) ?? tableView.dequeueReusableHeaderFooterViewWithIdentifier("customHeader")) as! CustomHeader
// Set up your header
return header
}
// Change text in header
let header = updateHeader(2).customLabel.text = "Your custom text"
Just a small example. Be aware that when the tableview gets reloaded, it will change back to your default setup of your header. So be sure you handle that within your own code.
You can do it without using titleForHeaderInSection
Make dynamic array 1st object for header custom cell then objects for tableview cell... and so on
Draft custom cell for header
While screen appear use cellForRowAtIndexPath with condition as per header custom cell and objects for table view
At particular event change array value for header custom cell then use reloadRowsAtIndexPaths()
So it will reload only index given in reloadRowsAtIndexPaths(), no need for reloadData().

tableView section headers disappear SWIFT

I have a tableView set up so that when a cell is touched, it expands in height to reveal more information. The tableView has 5 sections.
I have a bug: when a cell expands, all headersCells below that cell go invisible. The console outputs the following: "[31233:564745] no index path for table cell being reused"
In my storyboard I have 2 custom cells : "myCell" for the data bearing cells, and "headerCell" for the headers.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let thisGoal : GoalModel = goalArray[indexPath.section][indexPath.row]
if self.currentPath != nil && self.currentPath == indexPath {
self.currentPath = nil
} else {
self.currentPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
If I enter tableView.reloadData() in between the begin/end updates, it functions properly, although the header background turns black, and loses animation.
I have all of the stuff for headers declared in: func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
what am I missing? I'd really like the tableView to still have animations, and keep the background clearColor().
Thanks in advance. I did read through the objective C answers, but couldn't get them to work for me. I'd really appreciate some SWIFT help.
I think the problem is the no index path for table cell being reused.
I found an answer in the console output. Use this code in the header function:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
Do not return your headerCell, or your reusable identifier. Return the reuseIdentifier.contentView. For me it's: return headerCell!.contentView.
Just to add, I was baffled for WAY longer than I should have been as to why I couldn't refer to the contentView of my cell, when I could quite clearly see it was there. My custom class (using UITableViewCell rather than UITableViewHeaderFooterView) would return a fatal error each time. Therefore make sure any custom styling is setup under UITableViewHeaderFooterView class like:
class CustomHeaderCell: UITableViewHeaderFooterView {
You will also need to register the resuableIdentifer like this:
tableView.registerNib(UINib(nibName: "HeaderCell", bundle: nil), forHeaderFooterViewReuseIdentifier: "CellHeader")
Then this bad boy:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableHeaderFooterViewWithIdentifier("CellHeader") as! CustomHeaderCell!
return headerCell!.contentView
}
Since I'm not at 50 reputation yet, I can't comment on the previous answer, so I apologize for listing this as another answer.
Returning the ContentView will make the function work but will remove all formatting done to the reuseIdentifier (headerCell)
headerCell.backgroundColor = UIColor.cyanColor()
This will NOT provide a Cyan color to your headerCell
To fix this, just add the ".contentView" to your formatting lines
headerCell.contentView.backgroundColor = UIColor.cyanColor()
Table view headers in 2 tables disappeared when I converted my app to IOS 10 - I found the reason in Apple developer API documentation on table headers. When I added the following, the missing headers reappeared!
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return 44 // where 44 is the header cell view height in my storyboard
}
You could wrap the TableViewCell inside an UIView
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 50)) // 50 = Header height
guard let headerCell = tableView.dequeueReusableCell(withIdentifier: "MyHeaderView") as? MyHeaderView else { fatalError(" Failed to load MyHeaderView") }
headerCell.frame = containerView.bounds
containerView.addSubview(headerCell)
return containerView
}
I had the same bug because I was returning a cell using dequeue method instead of a UITableViewHeaderFooterView.
Solution:
Add a view outside of the view hierarchy
Set the type to UITableViewHeaderFooterView
Customize
Link to an #IBOutlet
In func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? return the outlet
Common pitfalls:
Don't forget to set the header sizes
Don't forget to set the outlet as strong.

Resources