How to Add HeaderView from nib to UiTableView programmatically in Swift - ios

Well i'm a naive IOS developer using swift language.
I have a table view which displays a list of features of a hotel.Now i want to add a header information to the tableView with hotel image, hotel name above the tableview so that the header information also scrolls along with tableview contents(product features list)
Here is the problem, TableView with list of features is working fine.
class HotelDetailViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var categoriesTableView: UITableView!
var items: [(String, String,String)] = [
("Service", " Courteous service, Good staff , Good staff"," Bad Facility"),
("Vibe", " Courteous service, Good staff"," Bad Facility"),
("Hotel", " Courteous service, Good staff"," Bad Facility"),
("Room Facility", " Courteous service, Good staff"," Bad Facility"),
("Amenities", " Courteous service, Good staff"," Bad Facility"),
("Food", " Courteous service, Good staff"," Bad Facility")
]
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "HotelSnippetTableViewCell", bundle: nil)
categoriesTableView.separatorStyle = UITableViewCellSeparatorStyle.None
categoriesTableView.registerNib(nib, forCellReuseIdentifier: "CustomCell")
categoriesTableView.rowHeight = UITableViewAutomaticDimension
categoriesTableView.estimatedRowHeight = 100
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let customTableViewCell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! HotelSnippetTableViewCell
let info = items[indexPath.row]
customTableViewCell.setCategory(info.0)
customTableViewCell.setPositive(info.1)
customTableViewCell.setNegative(info.2)
return customTableViewCell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
print("You selected cell #\(indexPath.row)!")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
}
For header information like hotelImage , hotel name and other labels i have created a nib with all the views set and a corresponding class with all the references mapped to it.
Now how to add this UiView to my tableview as a header programatically (because i get data after rest call).
I have searched but found tutorials on how to set string section headers.
Couple of things i tried out:
I found that there is a protocol function for TableView
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//Code}
But how to load my nib here and set to this tableview and also i want ti trigger this programatically only after getting the data from service
Create a nib with parent as TableViewCell, Load it using dequeueReusableCellWithIdentifier(), set info and set to tableview
let headerCell = categoriesTableView.dequeueReusableCellWithIdentifier("HotelDetailHeaderTableViewCell") as! HotelDetailHeaderTableViewCell headerCell.setImageUrl("");
headerCell.setHotelZmi(CGFloat(87))
headerCell.setNameAndPersona("Fort Aguada Goa", persona: "OverAll", reviewsCount: "780")
headerCell.frame = CGRectMake(0, 0, self.categoriesTableView.bounds.width, 600)
categoriesTableView.tableHeaderView = headerCell
But even this is throwing some exception and i can't see where the exception is(How to see exception log in Xcode?).
Is the process i'm doing is correct? If not anyone please suggest me efficient approach
Because in Android i used to create a NestedScrollView with linear layout inside it embedded with any number of recyclerViews(removing scroll for recyclerviews) and Relativelayout
Please Help me.

You can use this code to init your nib file
let view = (Bundle.main.loadNibNamed("MenuHeader", owner: self, options: nil)![0] as? UIView)
tableView.tableHeaderView = view
Then your MenuHeader will be a normal .xib file with your cell inside it, just remember to adjust the constraints appropriately to fit on all the screens that you want.

Update for swift 4
let view = (Bundle.main.loadNibNamed("TableViewHeader", owner: self, options: nil)![0] as? UIView)
self.tableview.tableHeaderView = view

I would recommend using the UITableViewDelegate methods of tableView(:viewForHeaderInSection:) and tableView(:heightForHeaderInSection:).
You can then do the following:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = Bundle.main.loadNibNamed("ExampleTableHeaderView", owner: self, options: nil)?.first as? UIView
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 10
}
Other answers involve force unwrapping (!) and placing in brackets which is not necessary.

In your viewForHeaderInSection load the custom view from xib and return it
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// for example you have xib as 'CustomHeaderView.xib'
let headerView = CustomView()
headerView.myLabel.text = "My Title"
return headerView
}
Another method is specifying the header height
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60.0 // put your height here
}

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.

Why do collapsing/expandable iOS UITableView rows disappear on interaction?

I am new to iOS Development and I just implemented a simple expandable sections UITableView. I am not able to understand why some rows disappear and sometimes change position when the row heights are recalculated on tapping the section header. I went through all the already answered questions on this topic and have not been able to find the right solution.
Following is a scenario:
Launch the app:
Tap on the section header:
Section expands
All other headers disappear
Tap again
Section collapses
The headers continue to be blank
Scrolled to the bottom and back to the top
The positions of headers changed
Scrolled to the bottom and back to the top again
The positions of headers changed again with some cells still blank
Things I have already tried:
Wrapping reloadRowsAtIndexPaths in updates block (beginUpdates() and endUpdates())
Using reloadRowsAtIndexPaths with animation set to .none
Removing reloadRowsAtIndexPaths at all while keeping the updates block
Using reloadData() instead which actually works but I lose animation
Code:
Here is the link to the project repository.
You're using cells for the header. You shouldn't do that, you need a regular UIView there, or at least a cell that's not being dequeued like that. There's a few warnings when you run it that give that away. Usually just make a standalone xib with the view and then have a static method like this in your header class. Make sure you tie your outlets to the view itself, and NOT the owner:
static func view() -> HeaderView {
return Bundle.main.loadNibNamed("HeaderView", owner: nil, options: nil)![0] as! HeaderView
}
You're reloading the cells in the section that grows, but when you change the section that's grown you'd need to at least reload the former section for it to take the changes to it's cell's height. You can reload the section by index instead of individual rows in both cases
Ok as you ask, I am changing my answer according to you.
import UIKit
class MyTableViewController: UITableViewController {
let rows = 2
var categories = [Int](repeating: 0, count: 10)
struct Constants {
static let noSelectedSection = -1
}
var selectedSection: Int = Constants.noSelectedSection
func selectedChanged(to selected: Int?) {
let oldIndex = selectedSection;
if let s = selected {
if selectedSection != s {
selectedSection = s
} else {
selectedSection = Constants.noSelectedSection
}
tableView.beginUpdates()
if(oldIndex != -1){
tableView.reloadSections([oldIndex,s], with: .automatic)
}else{
tableView.reloadSections([s], with: .automatic)
}
tableView.endUpdates()
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return categories.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("reloading section \(section)")
return (selectedSection == section) ? rows : 0;//rows
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return tableView.rowHeight
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.rowHeight
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "Header")
if let categoryCell = cell as? MyTableViewCell {
categoryCell.category = section + 1
let recognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
recognizer.numberOfTapsRequired = 1
recognizer.numberOfTouchesRequired = 1
categoryCell.contentView.tag = section;
categoryCell.contentView.addGestureRecognizer(recognizer)
}
return cell?.contentView
}
func handleTapGesture(recognizer: UITapGestureRecognizer) {
if let sindex = recognizer.view?.tag {
selectedChanged(to: sindex)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Body", for: indexPath)
if let label = cell.viewWithTag(1) as? UILabel {
label.text = "Body \(indexPath.section + 1) - \(indexPath.row + 1)"
}
return cell
}
}
As you can see now I am just reloading a particular section instead of reloading the whole table.
also, I have removed gesture recognizer from the cell & put this into the main controller.

How to register a UIView to be reused for UITable view header without using nib

I have created a UIView on my storyboard and I want to use this view for the header of my UITableView. I have gave an identifier on storyboard for this view and in viewForHeaderInSection class I dequeue this identifier as shown below. However, whenever I reload the table and I found the headerCell is a new instance. It is not reused in this case.
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! UserHomeHeaderCell
I have tried to register this class on viewDidLoad() method as below:
tableView.registerClass(UserHomeHeaderCell.classForCoder, forCellReuseIdentifier: "HeaderCell")
But all the cell's content which are linked with storyboard are nil. How should I reuse this header?
You have to register nib in the ViewDidLoad()
tableView.registerNib(UINib(nibName: "UserHomeHeaderCell" , bundle: nil), forHeaderFooterViewReuseIdentifier: HeaderCell)
try this:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! UserHomeHeaderCell
return headerCell.contentView
}

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.

Return two different sections in my UITableView

I'm trying to return two seperate .counts in my UITableView. I kind of know the logic behind it but not using the syntax right. How can I do this? What I want it to do is fill my tableview up with the both .counts. I would also like to put in a label on top of the first section and second section. Anything would help!
Here is what I have so far but I'm only getting the first section of cells. Why isnt it displaying both .counts?
func tableView(tableView: UITableView, numberOfSectionsInTableView: Int) -> Int{
return 2;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (section == 0){
return featSubNames.count
}
else{
return subCatNames.count
}
}
func tableView(tableView: UITableView!, viewForHeaderInSection section: Int) -> UIView! {
if (section == 0){
????
}
if (section == 1){
????
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
// Configure the cell
switch (indexPath.section) {
case 0:
cell.textLabel?.text = featSubName[indexPath.row]
cell.textLabel?.textColor = UIColor.whiteColor()
case 1:
cell.textLabel?.text = subCatNames[indexPath.row]
cell.textLabel?.textColor = UIColor.whiteColor()
default:
cell.textLabel?.text = "Other"
}
return cell
}
Your code seems to be right. To get the UIViews on the header of each section, you can return any object that inherits from a UIView(this doesn't mean that would be nice to). So, you can return a small container with a UIImageView and a UILabel, if you want this for example.
Your code for the viewHeader would be something like this:
func tableView(tableView: UITableView!, viewForHeaderInSection section: Int) -> UIView! {
// instantiate the view to be returned and placed correctly
let commonLabel = UILabel(frame: CGRectMake(0, 0, tableWidth, 30))
commonLabel.textColor = .blackColor()
commonLabel.textAlignment = .Center
// ... other settings in the properties
if section == 0 {
commonLabel.text = "section1"
// other settings for the first section
}
if section == 1 {
commonLabel.text = "section2"
// other settings ...
}
return commonLabel
}
Notes:
Be sure to set the container view's frame,for sectionHeaders the top view in the hierarchy don't accept auto layout.
Implementing the viewForHeaderInSection method is more accessible to custom UI's, you can use the TitleForHeaderInSection if you wish, but it's limited for more complex stuffs.
EDIT:
If you're using storyboards, you still can use this code, although it's not so elegant in terms of apps that use IB for the UI. For this, you might take a look at this link: How to Implement Custom Table View Section Headers and Footers with Storyboard
. This implements only the view using storyboards, but the delegate part must to be wrote.

Resources