Reload only one section header in UITableView - ios

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.

Related

Always keep cell on the top of uitableview

I have an application that is viewbased and I am adding a uitableview as a subview to the main view. The uitableview is full with class Category cells. Everything works fine, but I want to have “Quick notes” Category always on the top of the uitableview. This means when I reloadData() in the Array, “Quick Notes” is always with index 0 and it goes on the bottom of the uitableview. And when I create new cell I need it to go under the “Quick Notes” section.
Please help me, what code I need to achieve that functionality and where I need to put it. Thanks!
Edit:
Thats where I am adding "Quick Notes" to the Realm database.
private let categories = try! Realm()
private init() {
if categories.objects(Category.self).isEmpty {
createCategoryWith(title: "Quick Notes", color: "#FF0000", icon: "quickNotes")
}
}
Update the array in the ViewController:
func didCreateCategory(category: Category) {
RealmHandler.shared.createCategoryWith(title: category.title, color: category.color, icon: category.icon)
self.categories = RealmHandler.shared.getAllCategories()
tableView.reloadData()
}
DataSourceDelegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "categoryCell", for: indexPath)
cell.textLabel?.text = categories[categories.count - (1+indexPath.row)].getTitle()
cell.contentView.backgroundColor = hexStringToUIColor(hex: categories[categories.count-(1+indexPath.row)].getColor())
return cell
}
use viewForHeaderInSection delegate method of tableView.
tableView(_ tableView: UITableView, viewForHeaderInSection section: Int)
here you can define your header cell.
then implement your logic that what do you want to show on this cell.
this headerCell will be always on top of your tableView.

swift block selection of custom section header cell

I have a UITableView with custom section headers, made via the storyboard using a custom prototype cell with a Identifier of "headerCell", along with a Cocoa Touch Class called "HeaderViewCell" subclassing UITableViewCell.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! HeaderViewCell
headerCell.sectionTitle.text = viewModel.items[section].sectionTitle
headerCell.section = section
headerCell.delegate = self
return headerCell
}
The button in the cell fires a delegate func passing in the section that was assigned to it.
Everything works great- setting the title, tapping the button I needed, etc... EXCEPT that when you tap the blank space between the section title (on the left) and button (on the right), the section header highlights as if it's a cell in the section, and then performs the segue for the first row in the section.
Selection is set to "None" in the attributes inspector. If I toggle User Interaction Enabled, then the button does not work.
I've found lots of posts where people are trying to register taps on the section headers (answer: with tap gestures), but exhausted myself in search of how to block them. In the didSelectRow at delegate method, I see the same IndexPath I would as if I clicked on the row and not the header, so I can't block it from there.
Being that using a custom prototype cell is the most widely suggested response to a custom section header, I would have expected this to have been an issue for someone else as well. ?
"HeaderViewCell" subclassing UITableViewCell.
Stop right there. That's totally wrong. Your section header should not be a UITableViewCell. It should be a UITableViewHeaderFooterView (or a subclass thereof).
As soon as you make that change (along with any needed corresponding changes to registration of the header view type), your problem will go away.
Matt's answer should would work.
Create a Subclass of type UITableViewHeaderFooterView and name it CustomHeaderView
class CustomHeaderView: UITableViewHeaderFooterView {
// programmatically add the sectionTitle and whatever else inside here. Matt said there isn’t a storyboard or nib for a HeaderFooterView so do it programmatically
}
Then inside inside viewForHeaderInSection use tableView.dequeueReusableHeaderFooterView and cast it as the CustomHeaderView
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// don't forget to rename the identifier
let customHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "customHeaderView") as! CustomHeaderView
customHeaderView.sectionTitle.text = viewModel.items[section].sectionTitle
customHeaderView.section = section
customHeaderView.delegate = self
return customHeaderView
}
If not try this.
If you don't want the cell to highlight first set the selection style to .none:
Either set .selectionStyle = .none inside the HeaderCell itself
or
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! HeaderViewCell
headerCell.sectionTitle.text = viewModel.items[section].sectionTitle
headerCell.section = section
headerCell.delegate = self
headerCell.selectionStyle = .none // set it here
return headerCell
}
Then in didSelectRowAtIndexPath find out the type of cell that is being selected. If it's a HeaderCell then just return and the cell shouldn't push. If it's any of the other type of cells (eg PushCell) then those cells should perform the segue:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// if it's a HeaderCell then do nothing but print
if let _ = tableView.cellForRowAtIndexPath(indexPath) as? HeaderCell {
tableView.deselectRow(at: indexPath, animated: true)
print("+++++HeaderCell was tapped")
return // nothing should happen
}
// if it's a PushCell then push
if let _ = tableView.cellForRowAtIndexPath(indexPath) as? PushCell {
print("-----PushCell was tapped")
performSegue(withIdentifier...
// or if your using navigationController?.pushViewController(...
}
}

Swift 3 - Expandable table view cells without closing other ones

I am using Swift 3.
I've been following this tutorial: https://www.youtube.com/watch?v=VWgr_wNtGPM , supplemented by this answer on StackOverflow.
However, the way that this works is that if I click on a cell, it expands while hiding other cells. How do I make it such that when I expand it, the other already-expanded cells stay expanded?
The best approach I suggest you for achieving this in an elegant way is implementing it through UIStackView elements.
Take a look this post http://www.atomicbird.com/blog/uistackview-table-cells
if you wanna do this yourself, you could try this way.
first step is you should create a model list just like:
var cellsData: [CustomData] = [];
the CustomData seem like:
class CustomData {
var isExpanded: Bool = false;
// whatever other variables
}
then your custom cell should whatever look like but you must do something in the tableView:didSelectItemAt like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row;
self.cellsData[row].isExpanded = !self.cellsData[row].isExpanded;
self.tableView.reloadRows(at: [indexPath], with: .none); // or the other animations
}
then in the "tableView:cellForRowAt" seems like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell;
if(cell.isExpanded){
// do something when the cell is expanded
}else{
// do something when the cell is not expanded
}
}
remember, the cell is reusable, means if you have used the cell more than one time, then the cell will keep the state when it was used the last time.
You can use ExpyTableView, which makes an expandable section from your given header cell. Compatible down to iOS 8.0.
All you have to do is to import ExpyTableView and then:
class ViewController: ExpyTableViewDataSource, ExpyTableViewDelegate {
#IBOutlet weak var expandableTableView: ExpyTableView!
// First, set data source and delegate for your table view.
override func viewDidLoad() {
super.viewDidLoad()
expandableTableView.dataSource = self
expandableTableView.delegate = self
}
// Then return your expandable cell instance from expandingCell data source method.
func expandableCell(forSection section: Int, inTableView tableView: ExpyTableView) -> UITableViewCell {
// this cell will be displayed at IndexPath with section: section and row 0
}
}
You can see your former table view section is now an expandable table view section. You can also download the example project and see more detailed examples.

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

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